Merge remote-tracking branch 'upstream/develop' into sanitize-gcontact

This commit is contained in:
Michael 2019-10-15 10:10:12 +00:00
commit f1e7d97b8c
1219 changed files with 241816 additions and 178090 deletions

File diff suppressed because it is too large Load diff

194
src/App/Arguments.php Normal file
View 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);
}
}

417
src/App/BaseURL.php Normal file
View file

@ -0,0 +1,417 @@
<?php
namespace Friendica\App;
use Friendica\Core\Config\Configuration;
use Friendica\Util\Network;
use Friendica\Util\Strings;
/**
* A class which checks and contains the basic
* environment for the BaseURL (url, urlpath, ssl_policy, hostname, scheme)
*/
class BaseURL
{
/**
* No SSL necessary
*/
const SSL_POLICY_NONE = 0;
/**
* SSL is necessary
*/
const SSL_POLICY_FULL = 1;
/**
* SSL is optional, but preferred
*/
const SSL_POLICY_SELFSIGN = 2;
/**
* Define the Default SSL scheme
*/
const DEFAULT_SSL_SCHEME = self::SSL_POLICY_SELFSIGN;
/**
* The Friendica Config
*
* @var Configuration
*/
private $config;
/**
* The server side variables
*
* @var array
*/
private $server;
/**
* The hostname of the Base URL
*
* @var string
*/
private $hostname;
/**
* The SSL_POLICY of the Base URL
*
* @var int
*/
private $sslPolicy;
/**
* The URL sub-path of the Base URL
*
* @var string
*/
private $urlPath;
/**
* The full URL
*
* @var string
*/
private $url;
/**
* The current scheme of this call
*
* @var string
*/
private $scheme;
/**
* Returns the hostname of this node
*
* @return string
*/
public function getHostname()
{
return $this->hostname;
}
/**
* Returns the current scheme of this call
*
* @return string
*/
public function getScheme()
{
return $this->scheme;
}
/**
* Returns the SSL policy of this node
*
* @return int
*/
public function getSSLPolicy()
{
return $this->sslPolicy;
}
/**
* Returns the sub-path of this URL
*
* @return string
*/
public function getUrlPath()
{
return $this->urlPath;
}
/**
* Returns the full URL of this call
*
* Note: $ssl parameter value doesn't directly correlate with the resulting protocol
*
* @param bool $ssl True, if ssl should get used
*
* @return string
*/
public function get($ssl = false)
{
if ($this->sslPolicy === self::SSL_POLICY_SELFSIGN && $ssl) {
return Network::switchScheme($this->url);
}
return $this->url;
}
/**
* Save current parts of the base Url
*
* @param string? $hostname
* @param int? $sslPolicy
* @param string? $urlPath
*
* @return bool true, if successful
*/
public function save($hostname = null, $sslPolicy = null, $urlPath = null)
{
$currHostname = $this->hostname;
$currSSLPolicy = $this->sslPolicy;
$currURLPath = $this->urlPath;
if (!empty($hostname) && $hostname !== $this->hostname) {
if ($this->config->set('config', 'hostname', $hostname)) {
$this->hostname = $hostname;
} else {
return false;
}
}
if (isset($sslPolicy) && $sslPolicy !== $this->sslPolicy) {
if ($this->config->set('system', 'ssl_policy', $sslPolicy)) {
$this->sslPolicy = $sslPolicy;
} else {
$this->hostname = $currHostname;
$this->config->set('config', 'hostname', $this->hostname);
return false;
}
}
if (isset($urlPath) && $urlPath !== $this->urlPath) {
if ($this->config->set('system', 'urlpath', $urlPath)) {
$this->urlPath = $urlPath;
} else {
$this->hostname = $currHostname;
$this->sslPolicy = $currSSLPolicy;
$this->config->set('config', 'hostname', $this->hostname);
$this->config->set('system', 'ssl_policy', $this->sslPolicy);
return false;
}
}
$this->determineBaseUrl();
if (!$this->config->set('system', 'url', $this->url)) {
$this->hostname = $currHostname;
$this->sslPolicy = $currSSLPolicy;
$this->urlPath = $currURLPath;
$this->determineBaseUrl();
$this->config->set('config', 'hostname', $this->hostname);
$this->config->set('system', 'ssl_policy', $this->sslPolicy);
$this->config->set('system', 'urlpath', $this->urlPath);
return false;
}
return true;
}
/**
* Save the current url as base URL
*
* @param $url
*
* @return bool true, if the save was successful
*/
public function saveByURL($url)
{
$parsed = @parse_url($url);
if (empty($parsed)) {
return false;
}
$hostname = $parsed['host'];
if (!empty($hostname) && !empty($parsed['port'])) {
$hostname .= ':' . $parsed['port'];
}
$urlPath = null;
if (!empty($parsed['path'])) {
$urlPath = trim($parsed['path'], '\\/');
}
$sslPolicy = null;
if (!empty($parsed['scheme'])) {
if ($parsed['scheme'] == 'https') {
$sslPolicy = BaseURL::SSL_POLICY_FULL;
}
}
return $this->save($hostname, $sslPolicy, $urlPath);
}
/**
* Checks, if a redirect to the HTTPS site would be necessary
*
* @return bool
*/
public function checkRedirectHttps()
{
return $this->config->get('system', 'force_ssl') &&
($this->getScheme() == "http") &&
intval($this->getSSLPolicy()) == BaseURL::SSL_POLICY_FULL &&
strpos($this->get(), 'https://') === 0 &&
!empty($this->server['REQUEST_METHOD']) &&
$this->server['REQUEST_METHOD'] === 'GET';
}
/**
* @param Configuration $config The Friendica configuration
* @param array $server The $_SERVER array
*/
public function __construct(Configuration $config, array $server)
{
$this->config = $config;
$this->server = $server;
$this->determineSchema();
$this->checkConfig();
}
/**
* Check the current config during loading
*/
public function checkConfig()
{
$this->hostname = $this->config->get('config', 'hostname');
$this->urlPath = $this->config->get('system', 'urlpath');
$this->sslPolicy = $this->config->get('system', 'ssl_policy');
$this->url = $this->config->get('system', 'url');
if (empty($this->hostname)) {
$this->determineHostname();
if (!empty($this->hostname)) {
$this->config->set('config', 'hostname', $this->hostname);
}
}
if (!isset($this->urlPath)) {
$this->determineURLPath();
$this->config->set('system', 'urlpath', $this->urlPath);
}
if (!isset($this->sslPolicy)) {
if ($this->scheme == 'https') {
$this->sslPolicy = self::SSL_POLICY_FULL;
} else {
$this->sslPolicy = self::DEFAULT_SSL_SCHEME;
}
$this->config->set('system', 'ssl_policy', $this->sslPolicy);
}
if (empty($this->url)) {
$this->determineBaseUrl();
if (!empty($this->url)) {
$this->config->set('system', 'url', $this->url);
}
}
}
/**
* Determines the hostname of this node if not set already
*/
private function determineHostname()
{
$this->hostname = '';
if (!empty($this->server['SERVER_NAME'])) {
$this->hostname = $this->server['SERVER_NAME'];
if (!empty($this->server['SERVER_PORT']) && $this->server['SERVER_PORT'] != 80 && $this->server['SERVER_PORT'] != 443) {
$this->hostname .= ':' . $this->server['SERVER_PORT'];
}
}
}
/**
* Figure out if we are running at the top of a domain or in a sub-directory
*/
private function determineURLPath()
{
$this->urlPath = '';
/*
* The automatic path detection in this function is currently deactivated,
* see issue https://github.com/friendica/friendica/issues/6679
*
* The problem is that the function seems to be confused with some url.
* These then confuses the detection which changes the url path.
*/
/* Relative script path to the web server root
* Not all of those $_SERVER properties can be present, so we do by inverse priority order
*/
$relative_script_path = '';
$relative_script_path = defaults($this->server, 'REDIRECT_URL', $relative_script_path);
$relative_script_path = defaults($this->server, 'REDIRECT_URI', $relative_script_path);
$relative_script_path = defaults($this->server, 'REDIRECT_SCRIPT_URL', $relative_script_path);
$relative_script_path = defaults($this->server, 'SCRIPT_URL', $relative_script_path);
$relative_script_path = defaults($this->server, 'REQUEST_URI', $relative_script_path);
/* $relative_script_path gives /relative/path/to/friendica/module/parameter
* QUERY_STRING gives pagename=module/parameter
*
* To get /relative/path/to/friendica we perform dirname() for as many levels as there are slashes in the QUERY_STRING
*/
if (!empty($relative_script_path)) {
// Module
if (!empty($this->server['QUERY_STRING'])) {
$this->urlPath = trim(rdirname($relative_script_path, substr_count(trim($this->server['QUERY_STRING'], '/'), '/') + 1), '/');
} else {
// Root page
$this->urlPath = trim($relative_script_path, '/');
}
}
}
/**
* Determine the full URL based on all parts
*/
private function determineBaseUrl()
{
$scheme = 'http';
if ($this->sslPolicy == self::SSL_POLICY_FULL) {
$scheme = 'https';
}
$this->url = $scheme . '://' . $this->hostname . (!empty($this->urlPath) ? '/' . $this->urlPath : '');
}
/**
* Determine the scheme of the current used link
*/
private function determineSchema()
{
$this->scheme = 'http';
if (!empty($this->server['HTTPS']) ||
!empty($this->server['HTTP_FORWARDED']) && preg_match('/proto=https/', $this->server['HTTP_FORWARDED']) ||
!empty($this->server['HTTP_X_FORWARDED_PROTO']) && $this->server['HTTP_X_FORWARDED_PROTO'] == 'https' ||
!empty($this->server['HTTP_X_FORWARDED_SSL']) && $this->server['HTTP_X_FORWARDED_SSL'] == 'on' ||
!empty($this->server['FRONT_END_HTTPS']) && $this->server['FRONT_END_HTTPS'] == 'on' ||
!empty($this->server['SERVER_PORT']) && (intval($this->server['SERVER_PORT']) == 443) // XXX: reasonable assumption, but isn't this hardcoding too much?
) {
$this->scheme = 'https';
}
}
/**
* Removes the base url from an url. This avoids some mixed content problems.
*
* @param string $origURL
*
* @return string The cleaned url
*/
public function remove(string $origURL)
{
// Remove the hostname from the url if it is an internal link
$nurl = Strings::normaliseLink($origURL);
$base = Strings::normaliseLink($this->get());
$url = str_replace($base . '/', '', $nurl);
// if it is an external link return the orignal value
if ($url == Strings::normaliseLink($origURL)) {
return $origURL;
} else {
return $url;
}
}
}

View file

@ -2,8 +2,10 @@
namespace Friendica\App;
use Friendica\Core\Config;
use Friendica\Database\DBA;
use Detection\MobileDetect;
use Friendica\Core\Config\Cache\ConfigCache;
use Friendica\Database\Database;
use Friendica\Util\BasePath;
/**
* Mode of the current Friendica Node
@ -12,26 +14,44 @@ use Friendica\Database\DBA;
*/
class Mode
{
const LOCALCONFIGPRESENT = 1;
const DBAVAILABLE = 2;
const DBCONFIGAVAILABLE = 4;
const LOCALCONFIGPRESENT = 1;
const DBAVAILABLE = 2;
const DBCONFIGAVAILABLE = 4;
const MAINTENANCEDISABLED = 8;
/***
* @var int the mode of this Application
* @var int The mode of this Application
*
*/
private $mode;
/**
* @var string the basepath of the application
* @var bool True, if the call is a backend call
*/
private $basepath;
private $isBackend;
public function __construct($basepath = '')
/**
* @var bool True, if the call is a ajax call
*/
private $isAjax;
/**
* @var bool True, if the call is from a mobile device
*/
private $isMobile;
/**
* @var bool True, if the call is from a tablet device
*/
private $isTablet;
public function __construct(int $mode = 0, bool $isBackend = false, bool $isAjax = false, bool $isMobile = false, bool $isTablet = false)
{
$this->basepath = $basepath;
$this->mode = 0;
$this->mode = $mode;
$this->isBackend = $isBackend;
$this->isAjax = $isAjax;
$this->isMobile = $isMobile;
$this->isTablet = $isTablet;
}
/**
@ -41,41 +61,67 @@ 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 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 . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'local.ini.php')
&& !file_exists($this->basepath . DIRECTORY_SEPARATOR . '.htconfig.php')) {
return;
if (!$database->connected()) {
return new Mode($mode);
}
$this->mode |= Mode::LOCALCONFIGPRESENT;
$mode |= Mode::DBAVAILABLE;
if (!DBA::connected()) {
return;
if ($database->fetchFirst("SHOW TABLES LIKE 'config'") === false) {
return new Mode($mode);
}
$this->mode |= Mode::DBAVAILABLE;
$mode |= Mode::DBCONFIGAVAILABLE;
if (DBA::fetchFirst("SHOW TABLES LIKE 'config'") === false) {
return;
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($database->selectFirst('config', ['v'], ['cat' => 'system', 'k' => 'maintenance'])['v'])) {
return new Mode($mode);
}
$this->mode |= Mode::DBCONFIGAVAILABLE;
$mode |= Mode::MAINTENANCEDISABLED;
if (Config::get('system', 'maintenance')) {
return;
}
return new Mode($mode, $this->isBackend, $this->isAjax, $this->isMobile, $this->isTablet);
}
$this->mode |= Mode::MAINTENANCEDISABLED;
/**
* Checks if the site is called via a backend process
*
* @param bool $isBackend True, if the call is from a backend script (daemon, worker, ...)
* @param Module $module The pre-loaded module (just name, not class!)
* @param array $server The $_SERVER variable
* @param MobileDetect $mobileDetect The mobile detection library
*
* @return Mode returns the determined mode
*/
public function determineRunMode(bool $isBackend, Module $module, array $server, MobileDetect $mobileDetect)
{
$isBackend = $isBackend ||
$module->isBackend();
$isMobile = $mobileDetect->isMobile();
$isTablet = $mobileDetect->isTablet();
$isAjax = strtolower($server['HTTP_X_REQUESTED_WITH'] ?? '') == 'xmlhttprequest';
return new Mode($this->mode, $isBackend, $isAjax, $isMobile, $isTablet);
}
/**
@ -99,7 +145,7 @@ class Mode
public function isInstall()
{
return !$this->has(Mode::LOCALCONFIGPRESENT) ||
!$this->has(MODE::DBCONFIGAVAILABLE);
!$this->has(MODE::DBCONFIGAVAILABLE);
}
/**
@ -110,8 +156,48 @@ class Mode
public function isNormal()
{
return $this->has(Mode::LOCALCONFIGPRESENT) &&
$this->has(Mode::DBAVAILABLE) &&
$this->has(Mode::DBCONFIGAVAILABLE) &&
$this->has(Mode::MAINTENANCEDISABLED);
$this->has(Mode::DBAVAILABLE) &&
$this->has(Mode::DBCONFIGAVAILABLE) &&
$this->has(Mode::MAINTENANCEDISABLED);
}
/**
* Returns true, if the call is from a backend node (f.e. from a worker)
*
* @return bool Is it a backend call
*/
public function isBackend()
{
return $this->isBackend;
}
/**
* Check if request was an AJAX (xmlhttprequest) request.
*
* @return bool true if it was an AJAX request
*/
public function isAjax()
{
return $this->isAjax;
}
/**
* Check if request was a mobile request.
*
* @return bool true if it was an mobile request
*/
public function isMobile()
{
return $this->isMobile;
}
/**
* Check if request was a tablet request.
*
* @return bool true if it was an tablet request
*/
public function isTablet()
{
return $this->isTablet;
}
}

250
src/App/Module.php Normal file
View file

@ -0,0 +1,250 @@
<?php
namespace Friendica\App;
use Friendica\App;
use Friendica\BaseObject;
use Friendica\Core;
use Friendica\LegacyModule;
use Friendica\Module\Home;
use Friendica\Module\HTTPException\MethodNotAllowed;
use Friendica\Module\HTTPException\PageNotFound;
use Friendica\Network\HTTPException\MethodNotAllowedException;
use Friendica\Network\HTTPException\NotFoundException;
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 True, if the current module is a backend module
* @see Module::BACKEND_MODULES for a list
*/
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
*
* @return Module The module with the determined module
*/
public function determineModule(Arguments $args)
{
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 = in_array($module, Module::BACKEND_MODULES);;
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 \Exception
*/
public function determineClass(Arguments $args, Router $router, Core\Config\Configuration $config)
{
$printNotAllowedAddon = false;
$module_class = null;
/**
* 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.
**/
try {
$module_class = $router->getModuleClass($args->getCommand());
} 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;
}
}
}
/* 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 = $module_class ?: PageNotFound::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 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, 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']);
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']);
}
}

476
src/App/Page.php Normal file
View file

@ -0,0 +1,476 @@
<?php
namespace Friendica\App;
use ArrayAccess;
use DOMDocument;
use DOMXPath;
use Friendica\App;
use Friendica\Content\Nav;
use Friendica\Core\Config\Configuration;
use Friendica\Core\Config\PConfiguration;
use Friendica\Core\Hook;
use Friendica\Core\L10n\L10n;
use Friendica\Core\Renderer;
use Friendica\Core\Theme;
use Friendica\Module\Special\HTTPException as ModuleHTTPException;
use Friendica\Network\HTTPException;
/**
* 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
*/
private $stylesheets;
/**
* @var array Contains all scripts, which are added to the footer at last
*/
private $footerScripts;
/**
* @var array The page content, which are showed directly
*/
private $page;
/**
* @var string The basepath of the page
*/
private $basePath;
/**
* @param string $basepath The Page basepath
*/
public function __construct(string $basepath)
{
$this->basePath = $basepath;
$this->page = [
'aside' => '',
'bottom' => '',
'content' => '',
'footer' => '',
'htmlhead' => '',
'nav' => '',
'page_title' => '',
'right_aside' => '',
'template' => '',
'title' => ''
];
}
/**
* 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
*
* @see Page::initHead()
*
*/
public function registerStylesheet($path)
{
if (mb_strpos($path, $this->basePath . DIRECTORY_SEPARATOR) === 0) {
$path = mb_substr($path, mb_strlen($this->basePath . DIRECTORY_SEPARATOR));
}
$this->stylesheets[] = trim($path, '/');
}
/**
* Initializes Page->page['htmlhead'].
*
* Includes:
* - Page title
* - Favicons
* - Registered stylesheets (through App->registerStylesheet())
* - Infinite scroll data
* - head.tpl template
*
* @param App $app The Friendica App instance
* @param Module $module The loaded Friendica module
* @param L10n $l10n The l10n language instance
* @param Configuration $config The Friendica configuration
* @param PConfiguration $pConfig The Friendica personal configuration (for user)
*
* @throws HTTPException\InternalServerErrorException
*/
private function initHead(App $app, Module $module, L10n $l10n, Configuration $config, PConfiguration $pConfig)
{
$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 == '') {
$touch_icon = 'images/friendica-128.png';
}
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, [
'$local_user' => local_user(),
'$generator' => 'Friendica' . ' ' . FRIENDICA_VERSION,
'$delitem' => $l10n->t('Delete this item?'),
'$update_interval' => $interval,
'$shortcut_icon' => $shortcut_icon,
'$touch_icon' => $touch_icon,
'$block_public' => intval($config->get('system', 'block_public')),
'$stylesheets' => $this->stylesheets,
]) . $this->page['htmlhead'];
}
/**
* 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
* @param Mode $mode The Friendica runtime mode
* @param L10n $l10n The l10n instance
*
* @throws HTTPException\InternalServerErrorException
*/
private function initFooter(App $app, Mode $mode, L10n $l10n)
{
// If you're just visiting, let javascript take you home
if (!empty($_SESSION['visitor_home'])) {
$homebase = $_SESSION['visitor_home'];
} elseif (local_user()) {
$homebase = 'profile/' . $app->user['nickname'];
}
if (isset($homebase)) {
$this->page['footer'] .= '<script>var homebase="' . $homebase . '";</script>' . "\n";
}
/*
* Add a "toggle mobile" link if we're using a mobile device
*/
if ($mode->isMobile() || $mode->isTablet()) {
if (isset($_SESSION['show-mobile']) && !$_SESSION['show-mobile']) {
$link = 'toggle_mobile?address=' . urlencode(curPageURL());
} else {
$link = 'toggle_mobile?off=1&address=' . urlencode(curPageURL());
}
$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, [
'$footerScripts' => $this->footerScripts,
]) . $this->page['footer'];
}
/**
* 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 {
$moduleClass = $module->getClassName();
$arr = ['content' => $content];
Hook::callAll($moduleClass . '_mod_content', $arr);
$content = $arr['content'];
$arr = ['content' => call_user_func([$moduleClass, 'content'])];
Hook::callAll($moduleClass . '_mod_aftercontent', $arr);
$content .= $arr['content'];
} catch (HTTPException $e) {
$content = ModuleHTTPException::content($e);
}
// 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)
{
$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
*
* @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 Configuration $config The Configuration of this node
* @param PConfiguration $pconfig The personal/user configuration
*
* @throws HTTPException\InternalServerErrorException
*/
public function run(App $app, BaseURL $baseURL, Mode $mode, Module $module, L10n $l10n, Configuration $config, PConfiguration $pconfig)
{
$moduleName = $module->getName();
/* Create the page content.
* Calls all hooks which are including content operations
*
* Sets the $Page->page['content'] variable
*/
$this->initContent($module, $mode);
// 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);
}
/* 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
*/
$this->initFooter($app, $mode, $l10n);
if (!$mode->isAjax()) {
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;
$profile = $app->profile;
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.
*/
if (isset($_GET["mode"])) {
$template = Theme::getPathForFile($_GET["mode"] . '.php');
}
// If there is no page template use the default page template
if (empty($template)) {
$template = Theme::getPathForFile("default.php");
}
// Theme templates expect $a as an App instance
$a = $app;
// Used as is in view/php/default.php
$lang = $l10n->getCurrentLang();
/// @TODO Looks unsafe (remote-inclusion), is maybe not but Core\Theme::getPathForFile() uses file_exists() but does not escape anything
require_once $template;
}
}

187
src/App/Router.php Normal file
View file

@ -0,0 +1,187 @@
<?php
namespace Friendica\App;
use FastRoute\DataGenerator\GroupCountBased;
use FastRoute\Dispatcher;
use FastRoute\RouteCollector;
use FastRoute\RouteParser\Std;
use Friendica\Core\Hook;
use Friendica\Core\L10n;
use Friendica\Network\HTTPException;
/**
* Wrapper for FastRoute\Router
*
* This wrapper only makes use of a subset of the router features, mainly parses a route rule to return the relevant
* module class.
*
* Actual routes are defined in App->collectRoutes.
*
* @package Friendica\App
*/
class Router
{
const POST = 'POST';
const GET = 'GET';
const ALLOWED_METHODS = [
self::POST,
self::GET,
];
/** @var RouteCollector */
protected $routeCollector;
/**
* @var string The HTTP method
*/
private $httpMethod;
/**
* @param array $server The $_SERVER variable
* @param RouteCollector|null $routeCollector Optional the loaded Route collector
*/
public function __construct(array $server, RouteCollector $routeCollector = null)
{
$httpMethod = $server['REQUEST_METHOD'] ?? self::GET;
$this->httpMethod = in_array($httpMethod, self::ALLOWED_METHODS) ? $httpMethod : self::GET;
$this->routeCollector = isset($routeCollector) ?
$routeCollector :
new RouteCollector(new Std(), new GroupCountBased());
}
/**
* @param array $routes The routes to add to the Router
*
* @return self The router instance with the loaded routes
*
* @throws HTTPException\InternalServerErrorException In case of invalid configs
*/
public function addRoutes(array $routes)
{
$routeCollector = (isset($this->routeCollector) ?
$this->routeCollector :
new RouteCollector(new Std(), new GroupCountBased()));
foreach ($routes as $route => $config) {
if ($this->isGroup($config)) {
$this->addGroup($route, $config, $routeCollector);
} elseif ($this->isRoute($config)) {
$routeCollector->addRoute($config[1], $route, $config[0]);
} else {
throw new HTTPException\InternalServerErrorException("Wrong route config for route '" . print_r($route, true) . "'");
}
}
$this->routeCollector = $routeCollector;
return $this;
}
/**
* Adds a group of routes to a given group
*
* @param string $groupRoute The route of the group
* @param array $routes The routes of the group
* @param RouteCollector $routeCollector The route collector to add this group
*/
private function addGroup(string $groupRoute, array $routes, RouteCollector $routeCollector)
{
$routeCollector->addGroup($groupRoute, function (RouteCollector $routeCollector) use ($routes) {
foreach ($routes as $route => $config) {
if ($this->isGroup($config)) {
$this->addGroup($route, $config, $routeCollector);
} elseif ($this->isRoute($config)) {
$routeCollector->addRoute($config[1], $route, $config[0]);
}else {
throw new HTTPException\InternalServerErrorException("Wrong route config for route '" . print_r($route, true) . "'");
}
}
});
}
/**
* Returns true in case the config is a group config
*
* @param array $config
*
* @return bool
*/
private function isGroup(array $config)
{
return
is_array($config) &&
is_string(array_keys($config)[0]) &&
// This entry should NOT be a BaseModule
(substr(array_keys($config)[0], 0, strlen('Friendica\Module')) !== 'Friendica\Module') &&
// The second argument is an array (another routes)
is_array(array_values($config)[0]);
}
/**
* Returns true in case the config is a route config
*
* @param array $config
*
* @return bool
*/
private function isRoute(array $config)
{
return
// The config array should at least have one entry
!empty($config[0]) &&
// This entry should be a BaseModule
(substr($config[0], 0, strlen('Friendica\Module')) === 'Friendica\Module') &&
// Either there is no other argument
(empty($config[1]) ||
// Or the second argument is an array (HTTP-Methods)
is_array($config[1]));
}
/**
* The current route collector
*
* @return RouteCollector|null
*/
public function getRouteCollector()
{
return $this->routeCollector;
}
/**
* Returns the relevant module class name for the given page URI or NULL if no route rule matched.
*
* @param string $cmd The path component of the request URL without the query string
*
* @return string A Friendica\BaseModule-extending class name if a route rule matched
*
* @throws HTTPException\InternalServerErrorException
* @throws HTTPException\MethodNotAllowedException If a rule matched but the method didn't
* @throws HTTPException\NotFoundException If no rule matched
*/
public function getModuleClass($cmd)
{
// Add routes from addons
Hook::callAll('route_collection', $this->routeCollector);
$cmd = '/' . ltrim($cmd, '/');
$dispatcher = new \FastRoute\Dispatcher\GroupCountBased($this->routeCollector->getData());
$moduleClass = null;
$routeInfo = $dispatcher->dispatch($this->httpMethod, $cmd);
if ($routeInfo[0] === Dispatcher::FOUND) {
$moduleClass = $routeInfo[1];
} elseif ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) {
throw new HTTPException\MethodNotAllowedException(L10n::t('Method not allowed for this module. Allowed method(s): %s', implode(', ', $routeInfo[1])));
} else {
throw new HTTPException\NotFoundException(L10n::t('Page not found.'));
}
return $moduleClass;
}
}

View file

@ -3,7 +3,7 @@
namespace Friendica;
use Friendica\Core\L10n;
use Friendica\Core\System;
use Friendica\Core\Logger;
/**
* All modules in Friendica should extend BaseModule, although not all modules
@ -34,6 +34,8 @@ abstract class BaseModule extends BaseObject
*/
public static function rawContent()
{
// echo '';
// exit;
}
/**
@ -87,7 +89,7 @@ abstract class BaseModule extends BaseObject
*/
public static function getFormSecurityToken($typename = '')
{
$a = get_app();
$a = \get_app();
$timestamp = time();
$sec_hash = hash('whirlpool', $a->user['guid'] . $a->user['prvkey'] . session_id() . $timestamp . $typename);
@ -115,10 +117,10 @@ abstract class BaseModule extends BaseObject
$max_livetime = 10800; // 3 hours
$a = get_app();
$a = \get_app();
$x = explode('.', $hash);
if (time() > (IntVal($x[0]) + $max_livetime)) {
if (time() > (intval($x[0]) + $max_livetime)) {
return false;
}
@ -135,9 +137,9 @@ abstract class BaseModule extends BaseObject
public static function checkFormSecurityTokenRedirectOnError($err_redirect, $typename = '', $formname = 'form_security_token')
{
if (!self::checkFormSecurityToken($typename, $formname)) {
$a = get_app();
logger('checkFormSecurityToken failed: user ' . $a->user['guid'] . ' - form element ' . $typename);
logger('checkFormSecurityToken failed: _REQUEST data: ' . print_r($_REQUEST, true), LOGGER_DATA);
$a = \get_app();
Logger::log('checkFormSecurityToken failed: user ' . $a->user['guid'] . ' - form element ' . $typename);
Logger::log('checkFormSecurityToken failed: _REQUEST data: ' . print_r($_REQUEST, true), Logger::DATA);
notice(self::getFormSecurityStandardErrorMessage());
$a->internalRedirect($err_redirect);
}
@ -146,11 +148,11 @@ abstract class BaseModule extends BaseObject
public static function checkFormSecurityTokenForbiddenOnError($typename = '', $formname = 'form_security_token')
{
if (!self::checkFormSecurityToken($typename, $formname)) {
$a = get_app();
logger('checkFormSecurityToken failed: user ' . $a->user['guid'] . ' - form element ' . $typename);
logger('checkFormSecurityToken failed: _REQUEST data: ' . print_r($_REQUEST, true), LOGGER_DATA);
header('HTTP/1.1 403 Forbidden');
killme();
$a = \get_app();
Logger::log('checkFormSecurityToken failed: user ' . $a->user['guid'] . ' - form element ' . $typename);
Logger::log('checkFormSecurityToken failed: _REQUEST data: ' . print_r($_REQUEST, true), Logger::DATA);
throw new \Friendica\Network\HTTPException\ForbiddenException();
}
}
}

View file

@ -4,16 +4,34 @@
*/
namespace Friendica;
require_once 'boot.php';
require_once __DIR__ . '/../boot.php';
use Dice\Dice;
use Friendica\Network\HTTPException\InternalServerErrorException;
/**
* Basic object
*
* Contains what is useful to any object
*
* Act's like a global registry for classes
*/
class BaseObject
{
private static $app = null;
/**
* @var Dice The Dependency Injection library
*/
private static $dice;
/**
* Set's the dependency injection library for a global usage
*
* @param Dice $dice The dependency injection library
*/
public static function setDependencyInjection(Dice $dice)
{
self::$dice = $dice;
}
/**
* Get the app
@ -24,22 +42,28 @@ class BaseObject
*/
public static function getApp()
{
if (empty(self::$app)) {
self::$app = new App(dirname(__DIR__));
}
return self::$app;
return self::getClass(App::class);
}
/**
* Set the app
* Returns the initialized class based on it's name
*
* @param object $app App
* @param string $name The name of the class
*
* @return void
* @return object The initialized name
*
* @throws InternalServerErrorException
*/
public static function setApp(App $app)
protected static function getClass(string $name)
{
self::$app = $app;
if (empty(self::$dice)) {
throw new InternalServerErrorException('DICE isn\'t initialized.');
}
if (class_exists($name) || interface_exists($name )) {
return self::$dice->create($name);
} else {
throw new InternalServerErrorException('Class \'' . $name . '\' isn\'t valid.');
}
}
}

View file

@ -1,10 +1,11 @@
<?php
namespace Friendica\Core\Console;
namespace Friendica\Console;
use Friendica\App;
use Friendica\Core\L10n;
use Friendica\Database\DBA;
use Friendica\Database\Database;
use Friendica\Util\Strings;
use RuntimeException;
/**
@ -20,6 +21,19 @@ class ArchiveContact extends \Asika\SimpleConsole\Console
{
protected $helpOptions = ['h', 'help', '?'];
/**
* @var App\Mode
*/
private $appMode;
/**
* @var Database
*/
private $dba;
/**
* @var L10n\L10n
*/
private $l10n;
protected function getHelp()
{
$help = <<<HELP
@ -37,10 +51,17 @@ HELP;
return $help;
}
public function __construct(App\Mode $appMode, Database $dba, L10n\L10n $l10n, array $argv = null)
{
parent::__construct($argv);
$this->appMode = $appMode;
$this->dba = $dba;
$this->l10n = $l10n;
}
protected function doExecute()
{
$a = \Friendica\BaseObject::getApp();
if ($this->getOption('v')) {
$this->out('Class: ' . __CLASS__);
$this->out('Arguments: ' . var_export($this->args, true));
@ -56,18 +77,16 @@ HELP;
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
}
if ($a->getMode()->isInstall()) {
if ($this->appMode->isInstall()) {
throw new RuntimeException('Friendica isn\'t properly installed yet.');
}
$nurl = normalise_link($this->getArgument(0));
if (!DBA::exists('contact', ['nurl' => $nurl, 'archive' => false])) {
$nurl = Strings::normaliseLink($this->getArgument(0));
if (!$this->dba->exists('contact', ['nurl' => $nurl, 'archive' => false])) {
throw new RuntimeException(L10n::t('Could not find any unarchived contact entry for this URL (%s)', $nurl));
}
if (DBA::update('contact', ['archive' => true], ['nurl' => $nurl])) {
$condition = ["`cid` IN (SELECT `id` FROM `contact` WHERE `archive`)"];
DBA::delete('queue', $condition);
$this->out(L10n::t('The contact entries have been archived'));
if ($this->dba->update('contact', ['archive' => true], ['nurl' => $nurl])) {
$this->out($this->l10n->t('The contact entries have been archived'));
} else {
throw new RuntimeException('The contact archival failed.');
}

View file

@ -0,0 +1,300 @@
<?php
namespace Friendica\Console;
use Asika\SimpleConsole\Console;
use Friendica\App;
use Friendica\App\BaseURL;
use Friendica\Core\Config;
use Friendica\Core\Installer;
use Friendica\Core\Theme;
use Friendica\Database\Database;
use Friendica\Util\BasePath;
use Friendica\Util\ConfigFileLoader;
use RuntimeException;
class AutomaticInstallation extends Console
{
/**
* @var App\Mode
*/
private $appMode;
/**
* @var Config\Cache\ConfigCache
*/
private $configCache;
/**
* @var Config\Configuration
*/
private $config;
/**
* @var Database
*/
private $dba;
protected function getHelp()
{
return <<<HELP
Installation - Install Friendica automatically
Synopsis
bin/console autoinstall [-h|--help|-?] [-v] [-a] [-f]
Description
Installs Friendica with data based on the local.config.php file or environment variables
Notes
Not checking .htaccess/URL-Rewrite during CLI installation.
Options
-h|--help|-? Show help information
-v Show more debug information.
-a All setup checks are required (except .htaccess)
-f|--file <config> prepared config file (e.g. "config/local.config.php" itself) which will override every other config option - except the environment variables)
-s|--savedb Save the DB credentials to the file (if environment variables is used)
-H|--dbhost <host> The host of the mysql/mariadb database (env MYSQL_HOST)
-p|--dbport <port> The port of the mysql/mariadb database (env MYSQL_PORT)
-d|--dbdata <database> The name of the mysql/mariadb database (env MYSQL_DATABASE)
-U|--dbuser <username> The username of the mysql/mariadb database login (env MYSQL_USER or MYSQL_USERNAME)
-P|--dbpass <password> The password of the mysql/mariadb database login (env MYSQL_PASSWORD)
-U|--url <url> The full base URL of Friendica - f.e. 'https://friendica.local/sub' (env FRIENDICA_URL)
-B|--phppath <php_path> The path of the PHP binary (env FRIENDICA_PHP_PATH)
-b|--basepath <base_path> The basepath of Friendica (env FRIENDICA_BASE_PATH)
-t|--tz <timezone> The timezone of Friendica (env FRIENDICA_TZ)
-L|--lang <language> The language of Friendica (env FRIENDICA_LANG)
Environment variables
MYSQL_HOST The host of the mysql/mariadb database (mandatory if mysql and environment is used)
MYSQL_PORT The port of the mysql/mariadb database
MYSQL_USERNAME|MYSQL_USER The username of the mysql/mariadb database login (MYSQL_USERNAME is for mysql, MYSQL_USER for mariadb)
MYSQL_PASSWORD The password of the mysql/mariadb database login
MYSQL_DATABASE The name of the mysql/mariadb database
FRIENDICA_URL The full base URL of Friendica - f.e. 'https://friendica.local/sub'
FRIENDICA_PHP_PATH The path of the PHP binary - leave empty for auto detection
FRIENDICA_BASE_PATH The basepath of Friendica - leave empty for auto detection
FRIENDICA_ADMIN_MAIL The admin email address of Friendica (this email will be used for admin access)
FRIENDICA_TZ The timezone of Friendica
FRIENDICA_LANG The langauge of Friendica
Examples
bin/console autoinstall -f 'input.config.php
Installs Friendica with the prepared 'input.config.php' file
bin/console autoinstall --savedb
Installs Friendica with environment variables and saves them to the 'config/local.config.php' file
bin/console autoinstall -h localhost -p 3365 -U user -P passwort1234 -d friendica
Installs Friendica with a local mysql database with credentials
HELP;
}
public function __construct(App\Mode $appMode, Config\Cache\ConfigCache $configCache, Config\Configuration $config, Database $dba, array $argv = null)
{
parent::__construct($argv);
$this->appMode = $appMode;
$this->configCache =$configCache;
$this->config = $config;
$this->dba = $dba;
}
protected function doExecute()
{
// Initialise the app
$this->out("Initializing setup...\n");
$installer = new Installer();
$configCache = $this->configCache;
$basePathConf = $configCache->get('system', 'basepath');
$basepath = new BasePath($basePathConf);
$installer->setUpCache($configCache, $basepath->getPath());
$this->out(" Complete!\n\n");
// Check Environment
$this->out("Checking environment...\n");
$installer->resetChecks();
if (!$this->runBasicChecks($installer, $configCache)) {
$errorMessage = $this->extractErrors($installer->getChecks());
throw new RuntimeException($errorMessage);
}
$this->out(" Complete!\n\n");
// if a config file is set,
$config_file = $this->getOption(['f', 'file']);
if (!empty($config_file)) {
if (!file_exists($config_file)) {
throw new RuntimeException("ERROR: Config file does not exist.\n");
}
//reload the config cache
$loader = new ConfigFileLoader($config_file);
$loader->setupCache($configCache);
} else {
// Creating config file
$this->out("Creating config file...\n");
$save_db = $this->getOption(['s', 'savedb'], false);
$db_host = $this->getOption(['H', 'dbhost'], ($save_db) ? (getenv('MYSQL_HOST')) : Installer::DEFAULT_HOST);
$db_port = $this->getOption(['p', 'dbport'], ($save_db) ? getenv('MYSQL_PORT') : null);
$configCache->set('database', 'hostname', $db_host . (!empty($db_port) ? ':' . $db_port : ''));
$configCache->set('database', 'database',
$this->getOption(['d', 'dbdata'],
($save_db) ? getenv('MYSQL_DATABASE') : ''));
$configCache->set('database', 'username',
$this->getOption(['U', 'dbuser'],
($save_db) ? getenv('MYSQL_USER') . getenv('MYSQL_USERNAME') : ''));
$configCache->set('database', 'password',
$this->getOption(['P', 'dbpass'],
($save_db) ? getenv('MYSQL_PASSWORD') : ''));
$php_path = $this->getOption(['b', 'phppath'], !empty('FRIENDICA_PHP_PATH') ? getenv('FRIENDICA_PHP_PATH') : null);
if (!empty($php_path)) {
$configCache->set('config', 'php_path', $php_path);
} else {
$configCache->set('config', 'php_path', $installer->getPHPPath());
}
$configCache->set('config', 'admin_email',
$this->getOption(['A', 'admin'],
!empty(getenv('FRIENDICA_ADMIN_MAIL')) ? getenv('FRIENDICA_ADMIN_MAIL') : ''));
$configCache->set('system', 'default_timezone',
$this->getOption(['T', 'tz'],
!empty(getenv('FRIENDICA_TZ')) ? getenv('FRIENDICA_TZ') : Installer::DEFAULT_TZ));
$configCache->set('system', 'language',
$this->getOption(['L', 'lang'],
!empty(getenv('FRIENDICA_LANG')) ? getenv('FRIENDICA_LANG') : Installer::DEFAULT_LANG));
$basepath = $this->getOption(['b', 'basepath'], !empty(getenv('FRIENDICA_BASE_PATH')) ? getenv('FRIENDICA_BASE_PATH') : null);
if (!empty($basepath)) {
$configCache->set('system', 'basepath', $basepath);
}
$url = $this->getOption(['U', 'url'], !empty(getenv('FRIENDICA_URL')) ? getenv('FRIENDICA_URL') : null);
if (empty($url)) {
$this->out('The Friendica URL has to be set during CLI installation.');
return 1;
} else {
$baseUrl = new BaseURL($this->config, []);
$baseUrl->saveByURL($url);
}
$installer->createConfig($configCache);
}
$this->out("Complete!\n\n");
// Check database connection
$this->out("Checking database...\n");
$installer->resetChecks();
if (!$installer->checkDB($this->dba)) {
$errorMessage = $this->extractErrors($installer->getChecks());
throw new RuntimeException($errorMessage);
}
$this->out(" Complete!\n\n");
// Install database
$this->out("Inserting data into database...\n");
$installer->resetChecks();
if (!$installer->installDatabase($basePathConf)) {
$errorMessage = $this->extractErrors($installer->getChecks());
throw new RuntimeException($errorMessage);
}
if (!empty($config_file) && $config_file != 'config' . DIRECTORY_SEPARATOR . 'local.config.php') {
// Copy config file
$this->out("Copying config file...\n");
if (!copy($basePathConf . DIRECTORY_SEPARATOR . $config_file, $basePathConf . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'local.config.php')) {
throw new RuntimeException("ERROR: Saving config file failed. Please copy '$config_file' to '" . $basePathConf . "'" . DIRECTORY_SEPARATOR . "config" . DIRECTORY_SEPARATOR . "local.config.php' manually.\n");
}
}
$this->out(" Complete!\n\n");
// Install theme
$this->out("Installing theme\n");
if (!empty($this->config->get('system', 'theme'))) {
Theme::install($this->config->get('system', 'theme'));
$this->out(" Complete\n\n");
} else {
$this->out(" Theme setting is empty. Please check the file 'config/local.config.php'\n\n");
}
$this->out("\nInstallation is finished\n");
return 0;
}
/**
* @param Installer $installer The Installer instance
* @param Config\Cache\ConfigCache $configCache The config cache
*
* @return bool true if checks were successfully, otherwise false
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private function runBasicChecks(Installer $installer, Config\Cache\ConfigCache $configCache)
{
$checked = true;
$installer->resetChecks();
if (!$installer->checkFunctions()) {
$checked = false;
}
if (!$installer->checkImagick()) {
$checked = false;
}
if (!$installer->checkLocalIni()) {
$checked = false;
}
if (!$installer->checkSmarty3()) {
$checked = false;
}
if (!$installer->checkKeys()) {
$checked = false;
}
$php_path = $configCache->get('config', 'php_path');
if (!$installer->checkPHP($php_path, true)) {
$checked = false;
}
$this->out(" NOTICE: Not checking .htaccess/URL-Rewrite during CLI installation.\n");
return $checked;
}
/**
* @param array $results
* @return string
*/
private function extractErrors($results)
{
$errorMessage = '';
$allChecksRequired = $this->getOption('a') !== null;
foreach ($results as $result) {
if (($allChecksRequired || $result['required'] === true) && $result['status'] === false) {
$errorMessage .= "--------\n";
$errorMessage .= $result['title'] . ': ' . $result['help'] . "\n";
}
}
return $errorMessage;
}
}

View file

@ -1,10 +1,11 @@
<?php
namespace Friendica\Core\Console;
namespace Friendica\Console;
use Asika\SimpleConsole\CommandArgsException;
use Friendica\App;
use Friendica\Core;
use Friendica\Core\Cache\Cache as CacheClass;
use Friendica\Core\Cache\ICache;
use RuntimeException;
/**
@ -20,6 +21,16 @@ class Cache extends \Asika\SimpleConsole\Console
{
protected $helpOptions = ['h', 'help', '?'];
/**
* @var App\Mode
*/
private $appMode;
/**
* @var ICache
*/
private $cache;
protected function getHelp()
{
$help = <<<HELP
@ -54,10 +65,16 @@ HELP;
return $help;
}
public function __construct(App\Mode $appMode, ICache $cache, array $argv = null)
{
parent::__construct($argv);
$this->appMode = $appMode;
$this->cache = $cache;
}
protected function doExecute()
{
$a = \Friendica\BaseObject::getApp();
if ($this->getOption('v')) {
$this->out('Executable: ' . $this->executable);
$this->out('Class: ' . __CLASS__);
@ -65,15 +82,13 @@ HELP;
$this->out('Options: ' . var_export($this->options, true));
}
if ($a->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
if (!$this->appMode->has(App\Mode::DBCONFIGAVAILABLE)) {
$this->out('Database isn\'t ready or populated yet, database cache won\'t be available');
}
Core\Cache::init();
if ($this->getOption('v')) {
$this->out('Cache Driver Name: ' . Core\Cache::$driver_name);
$this->out('Cache Driver Class: ' . Core\Cache::$driver_class);
$this->out('Cache Driver Name: ' . $this->cache->getName());
$this->out('Cache Driver Class: ' . get_class($this->cache));
}
switch ($this->getArgument(0)) {
@ -105,7 +120,7 @@ HELP;
private function executeList()
{
$prefix = $this->getArgument(1);
$keys = Core\Cache::getAllKeys($prefix);
$keys = $this->cache->getAllKeys($prefix);
if (empty($prefix)) {
$this->out('Listing all cache keys:');
@ -125,8 +140,8 @@ HELP;
private function executeGet()
{
if (count($this->args) >= 2) {
$key = $this->getArgument(1);
$value = Core\Cache::get($key);
$key = $this->getArgument(1);
$value = $this->cache->get($key);
$this->out("{$key} => " . var_export($value, true));
} else {
@ -137,17 +152,17 @@ HELP;
private function executeSet()
{
if (count($this->args) >= 3) {
$key = $this->getArgument(1);
$value = $this->getArgument(2);
$duration = intval($this->getArgument(3, Core\Cache::FIVE_MINUTES));
$key = $this->getArgument(1);
$value = $this->getArgument(2);
$duration = intval($this->getArgument(3, CacheClass::FIVE_MINUTES));
if (is_array(Core\Cache::get($key))) {
if (is_array($this->cache->get($key))) {
throw new RuntimeException("$key is an array and can't be set using this command.");
}
$result = Core\Cache::set($key, $value, $duration);
$result = $this->cache->set($key, $value, $duration);
if ($result) {
$this->out("{$key} <= " . Core\Cache::get($key));
$this->out("{$key} <= " . $this->cache->get($key));
} else {
$this->out("Unable to set {$key}");
}
@ -158,7 +173,7 @@ HELP;
private function executeFlush()
{
$result = Core\Cache::clear();
$result = $this->cache->clear();
if ($result) {
$this->out('Cache successfully flushed');
} else {
@ -168,7 +183,7 @@ HELP;
private function executeClear()
{
$result = Core\Cache::clear(false);
$result = $this->cache->clear(false);
if ($result) {
$this->out('Cache successfully cleared');
} else {

View file

@ -1,10 +1,10 @@
<?php
namespace Friendica\Core\Console;
namespace Friendica\Console;
use Asika\SimpleConsole\CommandArgsException;
use Friendica\App;
use Friendica\Core;
use Friendica\Core\Config\Configuration;
use RuntimeException;
/**
@ -35,6 +35,15 @@ class Config extends \Asika\SimpleConsole\Console
{
protected $helpOptions = ['h', 'help', '?'];
/**
* @var App\Mode
*/
private $appMode;
/**
* @var Configuration
*/
private $config;
protected function getHelp()
{
$help = <<<HELP
@ -59,7 +68,7 @@ Description
Sets the value of the provided key in the category
Notes:
Setting config entries which are manually set in config/local.ini.php may result in
Setting config entries which are manually set in config/local.config.php may result in
conflict between database settings and the manual startup settings.
Options
@ -69,10 +78,16 @@ HELP;
return $help;
}
public function __construct(App\Mode $appMode, Configuration $config, array $argv = null)
{
parent::__construct($argv);
$this->appMode = $appMode;
$this->config = $config;
}
protected function doExecute()
{
$a = \Friendica\BaseObject::getApp();
if ($this->getOption('v')) {
$this->out('Executable: ' . $this->executable);
$this->out('Class: ' . __CLASS__);
@ -84,7 +99,7 @@ HELP;
throw new CommandArgsException('Too many arguments');
}
if (!$a->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
if (!$this->appMode->has(App\Mode::DBCONFIGAVAILABLE)) {
$this->out('Database isn\'t ready or populated yet, showing file config only');
}
@ -93,14 +108,14 @@ HELP;
$key = $this->getArgument(1);
$value = $this->getArgument(2);
if (is_array(Core\Config::get($cat, $key))) {
if (is_array($this->config->get($cat, $key))) {
throw new RuntimeException("$cat.$key is an array and can't be set using this command.");
}
$result = Core\Config::set($cat, $key, $value);
$result = $this->config->set($cat, $key, $value);
if ($result) {
$this->out("{$cat}.{$key} <= " .
Core\Config::get($cat, $key));
$this->config->get($cat, $key));
} else {
$this->out("Unable to set {$cat}.{$key}");
}
@ -109,11 +124,11 @@ HELP;
if (count($this->args) == 2) {
$cat = $this->getArgument(0);
$key = $this->getArgument(1);
$value = Core\Config::get($this->getArgument(0), $this->getArgument(1));
$value = $this->config->get($this->getArgument(0), $this->getArgument(1));
if (is_array($value)) {
foreach ($value as $k => $v) {
$this->out("{$cat}.{$key}[{$k}] => " . $v);
$this->out("{$cat}.{$key}[{$k}] => " . (is_array($v) ? implode(', ', $v) : $v));
}
} else {
$this->out("{$cat}.{$key} => " . $value);
@ -122,14 +137,16 @@ HELP;
if (count($this->args) == 1) {
$cat = $this->getArgument(0);
Core\Config::load($cat);
$this->config->load($cat);
$configCache = $this->config->getCache();
if (!is_null($a->config[$cat])) {
if ($configCache->get($cat) !== null) {
$this->out("[{$cat}]");
foreach ($a->config[$cat] as $key => $value) {
$catVal = $configCache->get($cat);
foreach ($catVal as $key => $value) {
if (is_array($value)) {
foreach ($value as $k => $v) {
$this->out("{$key}[{$k}] => " . $v);
$this->out("{$key}[{$k}] => " . (is_array($v) ? implode(', ', $v) : $v));
}
} else {
$this->out("{$key} => " . $value);
@ -141,18 +158,19 @@ HELP;
}
if (count($this->args) == 0) {
Core\Config::load();
$this->config->load();
if (Core\Config::get('system', 'config_adapter') == 'jit' && $a->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
if ($this->config->get('system', 'config_adapter') == 'jit' && $this->appMode->has(App\Mode::DBCONFIGAVAILABLE)) {
$this->out('Warning: The JIT (Just In Time) Config adapter doesn\'t support loading the entire configuration, showing file config only');
}
foreach ($a->config as $cat => $section) {
$config = $this->config->getCache()->getAll();
foreach ($config as $cat => $section) {
if (is_array($section)) {
foreach ($section as $key => $value) {
if (is_array($value)) {
foreach ($value as $k => $v) {
$this->out("{$cat}.{$key}[{$k}] => " . $v);
$this->out("{$cat}.{$key}[{$k}] => " . (is_array($v) ? implode(', ', $v) : $v));
}
} else {
$this->out("{$cat}.{$key} => " . $value);

View file

@ -1,6 +1,6 @@
<?php
namespace Friendica\Core\Console;
namespace Friendica\Console;
/**
* Description of CreateDoxygen

View file

@ -1,15 +1,13 @@
<?php
namespace Friendica\Core\Console;
namespace Friendica\Console;
use Friendica\Core;
use Friendica\Database\DBA;
use Friendica\Core\Config\Cache\ConfigCache;
use Friendica\Core\Update;
use Friendica\Database\Database;
use Friendica\Database\DBStructure;
use RuntimeException;
require_once 'boot.php';
require_once 'include/dba.php';
/**
* @brief Performs database updates from the command line
*
@ -19,12 +17,21 @@ class DatabaseStructure extends \Asika\SimpleConsole\Console
{
protected $helpOptions = ['h', 'help', '?'];
/**
* @var Database
*/
private $dba;
/**
* @var ConfigCache
*/
private $configCache;
protected function getHelp()
{
$help = <<<HELP
console dbstructure - Performs database updates
Usage
bin/console dbstructure <command> [-h|--help|-?] [-v]
bin/console dbstructure <command> [-h|--help|-?] |-f|--force] [-v]
Commands
dryrun Show database update schema queries without running them
@ -33,16 +40,24 @@ Commands
toinnodb Convert all tables from MyISAM to InnoDB
Options
-h|--help|-? Show help information
-v Show more debug information.
-h|--help|-? Show help information
-v Show more debug information.
-f|--force Force the update command (Even if the database structure matches)
-o|--override Override running or stalling updates
HELP;
return $help;
}
public function __construct(Database $dba, ConfigCache $configCache, $argv = null)
{
parent::__construct($argv);
$this->dba = $dba;
$this->configCache = $configCache;
}
protected function doExecute()
{
$a = get_app();
if ($this->getOption('v')) {
$this->out('Class: ' . __CLASS__);
$this->out('Arguments: ' . var_export($this->args, true));
@ -58,49 +73,24 @@ HELP;
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
}
if (!DBA::connected()) {
if (!$this->dba->isConnected()) {
throw new RuntimeException('Unable to connect to database');
}
Core\Config::load();
$basePath = $this->configCache->get('system', 'basepath');
switch ($this->getArgument(0)) {
case "dryrun":
$output = DBStructure::update(true, false);
$output = DBStructure::update($basePath, true, false);
break;
case "update":
$build = Core\Config::get('system', 'build');
if (empty($build)) {
Core\Config::set('system', 'build', DB_UPDATE_VERSION);
$build = DB_UPDATE_VERSION;
}
$stored = intval($build);
$current = intval(DB_UPDATE_VERSION);
// run the pre_update_nnnn functions in update.php
for ($x = $stored; $x < $current; $x ++) {
$r = run_update_function($x, 'pre_update');
if (!$r) {
break;
}
}
$output = DBStructure::update(true, true);
// run the update_nnnn functions in update.php
for ($x = $stored; $x < $current; $x ++) {
$r = run_update_function($x, 'update');
if (!$r) {
break;
}
}
Core\Config::set('system', 'build', DB_UPDATE_VERSION);
$force = $this->getOption(['f', 'force'], false);
$override = $this->getOption(['o', 'override'], false);
$output = Update::run($basePath, $force, $override,true, false);
break;
case "dumpsql":
ob_start();
DBStructure::printStructure();
DBStructure::printStructure($basePath);
$output = ob_get_clean();
break;
case "toinnodb":
@ -108,11 +98,12 @@ HELP;
DBStructure::convertToInnoDB();
$output = ob_get_clean();
break;
default:
$output = 'Unknown command: ' . $this->getArgument(0);
}
$this->out($output);
return 0;
}
}

View file

@ -1,6 +1,6 @@
<?php
namespace Friendica\Core\Console;
namespace Friendica\Console;
/**
* When I installed docblox, I had the experience that it does not generate any output at all.
@ -59,8 +59,7 @@ HELP;
throw new \RuntimeException('DocBlox isn\'t available.');
}
//return from util folder to frindica base dir
$dir = get_app()->getBasePath();
$dir = \get_app()->getBasePath();
//stack for dirs to search
$dirstack = [];
@ -130,8 +129,6 @@ HELP;
/**
* This function generates a comma separated list of file names.
*
* @package util
*
* @param array $fileset Set of file names
*
* @return string comma-separated list of the file names
@ -143,7 +140,6 @@ HELP;
/**
* This functions runs phpdoc on the provided list of files
* @package util
*
* @param array $fileset Set of filenames
*
@ -169,8 +165,6 @@ HELP;
*
* In that version, it does not necessarily generate the smallest set, because it may not alter the elements order enough.
*
* @package util
*
* @param array $fileset set of filenames
* @param int $ps number of files in subsets
*

View file

@ -1,6 +1,6 @@
<?php
namespace Friendica\Core\Console;
namespace Friendica\Console;
/**
* Extracts translation strings from the Friendica project's files to be exported

View file

@ -1,7 +1,8 @@
<?php
namespace Friendica\Core\Console;
namespace Friendica\Console;
use Friendica\App;
use Friendica\Core\L10n;
use Friendica\Model\Contact;
@ -20,15 +21,25 @@ class GlobalCommunityBlock extends \Asika\SimpleConsole\Console
{
protected $helpOptions = ['h', 'help', '?'];
/**
* @var App\Mode
*/
private $appMode;
/**
* @var L10n\L10n
*/
private $l10n;
protected function getHelp()
{
$help = <<<HELP
console globalcommunityblock - Block remote profile from interacting with this node
Usage
bin/console globalcommunityblock <profile_url> [-h|--help|-?] [-v]
bin/console globalcommunityblock <profile_url> [<reason>] [-h|--help|-?] [-v]
Description
Blocks an account in such a way that no postings or comments this account writes are accepted to this node.
You can provide a optional reason for the block.
Options
-h|--help|-? Show help information
@ -37,10 +48,16 @@ HELP;
return $help;
}
public function __construct(App\Mode $appMode, L10n $l10n, $argv = null)
{
parent::__construct($argv);
$this->appMode = $appMode;
$this->l10n = $l10n;
}
protected function doExecute()
{
$a = get_app();
if ($this->getOption('v')) {
$this->out('Class: ' . __CLASS__);
$this->out('Arguments: ' . var_export($this->args, true));
@ -52,20 +69,22 @@ HELP;
return 0;
}
if (count($this->args) > 1) {
if (count($this->args) > 2) {
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
}
if ($a->getMode()->isInstall()) {
if ($this->appMode->isInstall()) {
throw new \RuntimeException('Database isn\'t ready or populated yet');
}
$contact_id = Contact::getIdForURL($this->getArgument(0));
if (!$contact_id) {
throw new \RuntimeException(L10n::t('Could not find any contact entry for this URL (%s)', $nurl));
throw new \RuntimeException($this->l10n->t('Could not find any contact entry for this URL (%s)', $this->getArgument(0)));
}
if(Contact::block($contact_id)) {
$this->out(L10n::t('The contact has been blocked from the node'));
$block_reason = $this->getArgument(1);
if(Contact::block($contact_id, $block_reason)) {
$this->out($this->l10n->t('The contact has been blocked from the node'));
} else {
throw new \RuntimeException('The contact block failed.');
}

View file

@ -1,14 +1,12 @@
<?php
namespace Friendica\Core\Console;
namespace Friendica\Console;
use Friendica\Core\Protocol;
use Friendica\Database\DBA;
use Friendica\Network\Probe;
use Friendica\App;
use Friendica\Database\Database;
use Friendica\Model\Contact;
use RuntimeException;
require_once 'include/text.php';
/**
* @brief tool to silence accounts on the global community page
*
@ -26,6 +24,15 @@ class GlobalCommunitySilence extends \Asika\SimpleConsole\Console
{
protected $helpOptions = ['h', 'help', '?'];
/**
* @var App\Mode
*/
private $appMode;
/**
* @var Database
*/
private $dba;
protected function getHelp()
{
$help = <<<HELP
@ -46,10 +53,16 @@ HELP;
return $help;
}
public function __construct(App\Mode $appMode, Database $dba, array $argv = null)
{
parent::__construct($argv);
$this->appMode = $appMode;
$this->dba =$dba;
}
protected function doExecute()
{
$a = get_app();
if ($this->getOption('v')) {
$this->out('Class: ' . __CLASS__);
$this->out('Arguments: ' . var_export($this->args, true));
@ -65,27 +78,16 @@ HELP;
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
}
if ($a->getMode()->isInstall()) {
if ($this->appMode->isInstall()) {
throw new RuntimeException('Database isn\'t ready or populated yet');
}
/**
* 1. make nurl from last parameter
* 2. check DB (contact) if there is a contact with uid=0 and that nurl, get the ID
* 3. set the flag hidden=1 for the contact entry with the found ID
* */
$net = Probe::uri($this->getArgument(0));
if (in_array($net['network'], [Protocol::PHANTOM, Protocol::MAIL])) {
throw new RuntimeException('This account seems not to exist.');
}
$nurl = normalise_link($net['url']);
$contact = DBA::selectFirst("contact", ["id"], ["nurl" => $nurl, "uid" => 0]);
if (DBA::isResult($contact)) {
DBA::update("contact", ["hidden" => true], ["id" => $contact["id"]]);
$this->out('NOTICE: The account should be silenced from the global community page');
$contact_id = Contact::getIdForURL($this->getArgument(0));
if ($contact_id) {
$this->dba->update('contact', ['hidden' => true], ['id' => $contact_id]);
$this->out('The account has been successfully silenced from the global community page.');
} else {
throw new RuntimeException('NOTICE: Could not find any entry for this URL (' . $nurl . ')');
throw new RuntimeException('Could not find any public contact entry for this URL (' . $this->getArgument(0) . ')');
}
return 0;

185
src/Console/Lock.php Normal file
View file

@ -0,0 +1,185 @@
<?php
namespace Friendica\Console;
use Asika\SimpleConsole\CommandArgsException;
use Friendica\App;
use Friendica\Core\Lock\ILock;
use RuntimeException;
/**
* @brief tool to access the locks from the CLI
*
* With this script you can access the locks of your node from the CLI.
* You can read current locks and set/remove locks.
*
* @author Philipp Holzer <admin@philipp.info>, Hypolite Petovan <hypolite@mrpetovan.com>
*/
class Lock extends \Asika\SimpleConsole\Console
{
protected $helpOptions = ['h', 'help', '?'];
/**
* @var App\Mode
*/
private $appMode;
/**
* @var ILock
*/
private $lock;
protected function getHelp()
{
$help = <<<HELP
console lock - Manage node locks
Synopsis
bin/console lock list [<prefix>] [-h|--help|-?] [-v]
bin/console lock set <lock> [<timeout> [<ttl>]] [-h|--help|-?] [-v]
bin/console lock del <lock> [-h|--help|-?] [-v]
bin/console lock clear [-h|--help|-?] [-v]
Description
bin/console lock list [<prefix>]
List all locks, optionally filtered by a prefix
bin/console lock set <lock> [<timeout> [<ttl>]]
Sets manually a lock, optionally with the provided TTL (time to live) with a default of five minutes.
bin/console lock del <lock>
Deletes a lock.
bin/console lock clear
Clears all locks
Options
-h|--help|-? Show help information
-v Show more debug information.
HELP;
return $help;
}
public function __construct(App\Mode $appMode, ILock $lock, array $argv = null)
{
parent::__construct($argv);
$this->appMode = $appMode;
$this->lock = $lock;
}
protected function doExecute()
{
if ($this->getOption('v')) {
$this->out('Executable: ' . $this->executable);
$this->out('Class: ' . __CLASS__);
$this->out('Arguments: ' . var_export($this->args, true));
$this->out('Options: ' . var_export($this->options, true));
}
if (!$this->appMode->has(App\Mode::DBCONFIGAVAILABLE)) {
$this->out('Database isn\'t ready or populated yet, database cache won\'t be available');
}
if ($this->getOption('v')) {
$this->out('Lock Driver Name: ' . $this->lock->getName());
$this->out('Lock Driver Class: ' . get_class($this->lock));
}
switch ($this->getArgument(0)) {
case 'list':
$this->executeList();
break;
case 'set':
$this->executeSet();
break;
case 'del':
$this->executeDel();
break;
case 'clear':
$this->executeClear();
break;
}
if (count($this->args) == 0) {
$this->out($this->getHelp());
return 0;
}
return 0;
}
private function executeList()
{
$prefix = $this->getArgument(1, '');
$keys = $this->lock->getLocks($prefix);
if (empty($prefix)) {
$this->out('Listing all Locks:');
} else {
$this->out('Listing all Locks starting with "' . $prefix . '":');
}
$count = 0;
foreach ($keys as $key) {
$this->out($key);
$count++;
}
$this->out($count . ' locks found');
}
private function executeDel()
{
if (count($this->args) >= 2) {
$lock = $this->getArgument(1);
if ($this->lock->releaseLock($lock, true)) {
$this->out(sprintf('Lock \'%s\' released.', $lock));
} else {
$this->out(sprintf('Couldn\'t release Lock \'%s\'', $lock));
}
} else {
throw new CommandArgsException('Too few arguments for del.');
}
}
private function executeSet()
{
if (count($this->args) >= 2) {
$lock = $this->getArgument(1);
$timeout = intval($this->getArgument(2, false));
$ttl = intval($this->getArgument(3, false));
if ($this->lock->isLocked($lock)) {
throw new RuntimeException(sprintf('\'%s\' is already set.', $lock));
}
if (!empty($ttl) && !empty($timeout)) {
$result = $this->lock->acquireLock($lock, $timeout, $ttl);
} elseif (!empty($timeout)) {
$result = $this->lock->acquireLock($lock, $timeout);
} else {
$result = $this->lock->acquireLock($lock);
}
if ($result) {
$this->out(sprintf('Lock \'%s\' acquired.', $lock));
} else {
throw new RuntimeException(sprintf('Unable to lock \'%s\'.', $lock));
}
} else {
throw new CommandArgsException('Too few arguments for set.');
}
}
private function executeClear()
{
$result = $this->lock->releaseAll(true);
if ($result) {
$this->out('Locks successfully cleared.');
} else {
throw new RuntimeException('Unable to clear the locks.');
}
}
}

View file

@ -1,11 +1,9 @@
<?php
namespace Friendica\Core\Console;
namespace Friendica\Console;
use Friendica\Core;
require_once 'boot.php';
require_once 'include/dba.php';
use Friendica\App;
use Friendica\Core\Config\Configuration;
/**
* @brief Sets maintenance mode for this node
@ -16,6 +14,15 @@ class Maintenance extends \Asika\SimpleConsole\Console
{
protected $helpOptions = ['h', 'help', '?'];
/**
* @var App\Mode
*/
private $appMode;
/**
* @var Configuration
*/
private $config;
protected function getHelp()
{
$help = <<<HELP
@ -45,10 +52,16 @@ HELP;
return $help;
}
public function __construct(App\Mode $appMode, Configuration $config, $argv = null)
{
parent::__construct($argv);
$this->appMode = $appMode;
$this->config = $config;
}
protected function doExecute()
{
$a = \Friendica\BaseObject::getApp();
if ($this->getOption('v')) {
$this->out('Class: ' . __CLASS__);
$this->out('Arguments: ' . var_export($this->args, true));
@ -64,20 +77,20 @@ HELP;
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
}
if ($a->getMode()->isInstall()) {
if ($this->appMode->isInstall()) {
throw new \RuntimeException('Database isn\'t ready or populated yet');
}
$enabled = intval($this->getArgument(0));
Core\Config::set('system', 'maintenance', $enabled);
$this->config->set('system', 'maintenance', $enabled);
$reason = $this->getArgument(1);
if ($enabled && $this->getArgument(1)) {
Core\Config::set('system', 'maintenance_reason', $this->getArgument(1));
$this->config->set('system', 'maintenance_reason', $this->getArgument(1));
} else {
Core\Config::set('system', 'maintenance_reason', '');
$this->config->set('system', 'maintenance_reason', '');
}
if ($enabled) {

View file

@ -1,10 +1,10 @@
<?php
namespace Friendica\Core\Console;
namespace Friendica\Console;
use Friendica\Core\Config;
use Friendica\Core\L10n;
use Friendica\Database\DBA;
use Friendica\App;
use Friendica\Core\L10n\L10n;
use Friendica\Database\Database;
use Friendica\Model\User;
use RuntimeException;
@ -21,6 +21,19 @@ class NewPassword extends \Asika\SimpleConsole\Console
{
protected $helpOptions = ['h', 'help', '?'];
/**
* @var App\Mode
*/
private $appMode;
/**
* @var L10n
*/
private $l10n;
/**
* @var Database
*/
private $dba;
protected function getHelp()
{
$help = <<<HELP
@ -38,10 +51,17 @@ HELP;
return $help;
}
public function __construct(App\Mode $appMode, L10n $l10n, Database $dba, array $argv = null)
{
parent::__construct($argv);
$this->appMode = $appMode;
$this->l10n = $l10n;
$this->dba = $dba;
}
protected function doExecute()
{
$a = get_app();
if ($this->getOption('v')) {
$this->out('Class: ' . __CLASS__);
$this->out('Arguments: ' . var_export($this->args, true));
@ -57,37 +77,35 @@ HELP;
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
}
if ($a->getMode()->isInstall()) {
if ($this->appMode->isInstall()) {
throw new RuntimeException('Database isn\'t ready or populated yet');
}
$nick = $this->getArgument(0);
$user = DBA::selectFirst('user', ['uid'], ['nickname' => $nick]);
if (!DBA::isResult($user)) {
throw new RuntimeException(L10n::t('User not found'));
$user = $this->dba->selectFirst('user', ['uid'], ['nickname' => $nick]);
if (!$this->dba->isResult($user)) {
throw new RuntimeException($this->l10n->t('User not found'));
}
$password = $this->getArgument(1);
if (is_null($password)) {
$this->out(L10n::t('Enter new password: '), false);
$this->out($this->l10n->t('Enter new password: '), false);
$password = \Seld\CliPrompt\CliPrompt::hiddenPrompt(true);
}
if (!$password) {
throw new RuntimeException(L10n::t('Password can\'t be empty'));
}
try {
$result = User::updatePassword($user['uid'], $password);
if (!Config::get('system', 'disable_password_exposed', false) && User::isPasswordExposed($password)) {
throw new RuntimeException(L10n::t('The new password has been exposed in a public data dump, please choose another.'));
}
if (!$this->dba->isResult($result)) {
throw new \Exception($this->l10n->t('Password update failed. Please try again.'));
}
if (!User::updatePassword($user['uid'], $password)) {
throw new RuntimeException(L10n::t('Password update failed. Please try again.'));
$this->out($this->l10n->t('Password changed.'));
} catch (\Exception $e) {
throw new RuntimeException($e->getMessage(), $e->getCode(), $e);
}
$this->out(L10n::t('Password changed.'));
return 0;
}
}

View file

@ -1,6 +1,6 @@
<?php
namespace Friendica\Core\Console;
namespace Friendica\Console;
/**
* Read a strings.php file and create messages.po in the same directory
@ -27,7 +27,7 @@ Description
Options
-p <n> Number of plural forms. Default: 2
--base <file> Path to base messages.po file. Default: util/messages.po
--base <file> Path to base messages.po file. Default: view/lang/C/messages.po
-h|--help|-? Show help information
-v Show more debug information.
HELP;
@ -51,7 +51,7 @@ HELP;
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
}
$a = get_app();
$a = \get_app();
$phpfile = realpath($this->getArgument(0));
@ -107,7 +107,7 @@ HELP;
$out .= sprintf('"Plural-Forms: nplurals=%s; plural=%s;\n"', $lang_pnum, $lang_logic) . "\n";
$out .= "\n";
$base_path = $this->getOption('base', 'util' . DIRECTORY_SEPARATOR . 'messages.po');
$base_path = $this->getOption('base', 'view/lang/C/messages.po');
// load base messages.po and extract msgids
$base_msgids = [];
@ -206,6 +206,9 @@ HELP;
* - replace " with \"
* - replace tab char with \t
* - manage multiline strings
*
* @param string $str
* @return string
*/
private function massageString($str)
{

View file

@ -1,6 +1,6 @@
<?php
namespace Friendica\Core\Console;
namespace Friendica\Console;
/**
* Read a messages.po file and create strings.php in the same directory
@ -47,8 +47,6 @@ HELP;
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
}
$a = get_app();
$pofile = realpath($this->getArgument(0));
if (!file_exists($pofile)) {
@ -61,7 +59,7 @@ HELP;
$outfile = dirname($pofile) . DIRECTORY_SEPARATOR . 'strings.php';
if (strstr($outfile, 'util')) {
if (basename(dirname($pofile)) == 'C') {
$lang = 'en';
} else {
$lang = str_replace('-', '_', basename(dirname($pofile)));
@ -106,7 +104,6 @@ HELP;
}
if ($inv) {
$inv = false;
$out .= '"' . $v . '"';
}
@ -179,7 +176,6 @@ HELP;
}
if ($inv) {
$inv = false;
$out .= '"' . $v . '"';
}

View file

@ -0,0 +1,95 @@
<?php
namespace Friendica\Console;
use Friendica\App;
use Friendica\Core\Config\Configuration;
use Friendica\Core\L10n\L10n;
use Friendica\Core\Update;
/**
* Performs database post updates
*
* License: AGPLv3 or later, same as Friendica
*
* @author Tobias Diekershoff <tobias.diekershoff@gmx.net>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class PostUpdate extends \Asika\SimpleConsole\Console
{
protected $helpOptions = ['h', 'help', '?'];
/**
* @var App\Mode
*/
private $appMode;
/**
* @var Configuration
*/
private $config;
/**
* @var L10n
*/
private $l10n;
protected function getHelp()
{
$help = <<<HELP
console postupdate - Performs database post updates
Usage
bin/console postupdate [-h|--help|-?] [--reset <version>]
Options
-h|--help|-? Show help information
--reset <version> Reset the post update version
HELP;
return $help;
}
public function __construct(App\Mode $appMode, Configuration $config, L10n $l10n, array $argv = null)
{
parent::__construct($argv);
$this->appMode = $appMode;
$this->config = $config;
$this->l10n = $l10n;
}
protected function doExecute()
{
$a = \Friendica\BaseObject::getApp();
if ($this->getOption($this->helpOptions)) {
$this->out($this->getHelp());
return 0;
}
$reset_version = $this->getOption('reset');
if (is_bool($reset_version)) {
$this->out($this->getHelp());
return 0;
} elseif ($reset_version) {
$this->config->set('system', 'post_update_version', $reset_version);
echo $this->l10n->t('Post update version number has been set to %s.', $reset_version) . "\n";
return 0;
}
if ($this->appMode->isInstall()) {
throw new \RuntimeException('Database isn\'t ready or populated yet');
}
echo $this->l10n->t('Check for pending update actions.') . "\n";
Update::run($a->getBasePath(), true, false, true, false);
echo $this->l10n->t('Done.') . "\n";
echo $this->l10n->t('Execute pending post updates.') . "\n";
while (!\Friendica\Database\PostUpdate::update()) {
echo '.';
}
echo "\n" . $this->l10n->t('All pending post updates are done.') . "\n";
return 0;
}
}

185
src/Console/ServerBlock.php Normal file
View file

@ -0,0 +1,185 @@
<?php
namespace Friendica\Console;
use Asika\SimpleConsole\CommandArgsException;
use Asika\SimpleConsole\Console;
use Console_Table;
use Friendica\Core\Config\Configuration;
/**
* @brief Manage blocked servers
*
* With this tool, you can list the current blocked servers
* or you can add / remove a blocked server from the list
*/
class ServerBlock extends Console
{
const DEFAULT_REASON = 'blocked';
protected $helpOptions = ['h', 'help', '?'];
/**
* @var Configuration
*/
private $config;
protected function getHelp()
{
$help = <<<HELP
console serverblock - Manage blocked server domain patterns
Usage
bin/console serverblock [-h|--help|-?] [-v]
bin/console serverblock add <pattern> <reason> [-h|--help|-?] [-v]
bin/console serverblock remove <pattern> [-h|--help|-?] [-v]
Description
With this tool, you can list the current blocked server domain patterns
or you can add / remove a blocked server domain pattern from the list.
Patterns are case-insensitive shell wildcard comprising the following special characters:
- * : Any number of characters
- ? : Any single character
- [<char1><char2>...] : char1 or char2 or...
Options
-h|--help|-? Show help information
-v Show more debug information.
HELP;
return $help;
}
public function __construct(Configuration $config, $argv = null)
{
parent::__construct($argv);
$this->config = $config;
}
protected function doExecute()
{
if (count($this->args) == 0) {
$this->printBlockedServers($this->config);
return 0;
}
switch ($this->getArgument(0)) {
case 'add':
return $this->addBlockedServer($this->config);
case 'remove':
return $this->removeBlockedServer($this->config);
default:
throw new CommandArgsException('Unknown command.');
break;
}
}
/**
* Prints the whole list of blocked domains including the reason
*
* @param Configuration $config
*/
private function printBlockedServers(Configuration $config)
{
$table = new Console_Table();
$table->setHeaders(['Domain', 'Reason']);
$blocklist = $config->get('system', 'blocklist', []);
foreach ($blocklist as $domain) {
$table->addRow($domain);
}
$this->out($table->getTable());
}
/**
* Adds a server to the blocked list
*
* @param Configuration $config
*
* @return int The return code (0 = success, 1 = failed)
*/
private function addBlockedServer(Configuration $config)
{
if (count($this->args) < 2 || count($this->args) > 3) {
throw new CommandArgsException('Add needs a domain and optional a reason.');
}
$domain = $this->getArgument(1);
$reason = (count($this->args) === 3) ? $this->getArgument(2) : self::DEFAULT_REASON;
$update = false;
$currBlocklist = $config->get('system', 'blocklist', []);
$newBlockList = [];
foreach ($currBlocklist as $blocked) {
if ($blocked['domain'] === $domain) {
$update = true;
$newBlockList[] = [
'domain' => $domain,
'reason' => $reason,
];
} else {
$newBlockList[] = $blocked;
}
}
if (!$update) {
$newBlockList[] = [
'domain' => $domain,
'reason' => $reason,
];
}
if ($config->set('system', 'blocklist', $newBlockList)) {
if ($update) {
$this->out(sprintf("The domain '%s' is now updated. (Reason: '%s')", $domain, $reason));
} else {
$this->out(sprintf("The domain '%s' is now blocked. (Reason: '%s')", $domain, $reason));
}
return 0;
} else {
$this->out(sprintf("Couldn't save '%s' as blocked server", $domain));
return 1;
}
}
/**
* Removes a server from the blocked list
*
* @param Configuration $config
*
* @return int The return code (0 = success, 1 = failed)
*/
private function removeBlockedServer(Configuration $config)
{
if (count($this->args) !== 2) {
throw new CommandArgsException('Remove needs a second parameter.');
}
$domain = $this->getArgument(1);
$found = false;
$currBlocklist = $config->get('system', 'blocklist', []);
$newBlockList = [];
foreach ($currBlocklist as $blocked) {
if ($blocked['domain'] === $domain) {
$found = true;
} else {
$newBlockList[] = $blocked;
}
}
if (!$found) {
$this->out(sprintf("The domain '%s' is not blocked.", $domain));
return 1;
}
if ($config->set('system', 'blocklist', $newBlockList)) {
$this->out(sprintf("The domain '%s' is not more blocked", $domain));
return 0;
} else {
$this->out(sprintf("Couldn't remove '%s' from blocked servers", $domain));
return 1;
}
}
}

147
src/Console/Storage.php Normal file
View file

@ -0,0 +1,147 @@
<?php
namespace Friendica\Console;
use Asika\SimpleConsole\CommandArgsException;
use Friendica\Core\StorageManager;
/**
* @brief tool to manage storage backend and stored data from CLI
*
*/
class Storage extends \Asika\SimpleConsole\Console
{
protected $helpOptions = ['h', 'help', '?'];
protected function getHelp()
{
$help = <<<HELP
console storage - manage storage backend and stored data
Synopsis
bin/console storage [-h|--help|-?] [-v]
Show this help
bin/console storage list
List available storage backends
bin/console storage set <name>
Set current storage backend
name storage backend to use. see "list".
bin/console storage move [table] [-n 5000]
Move stored data to current storage backend.
table one of "photo" or "attach". default to both
-n limit of processed entry batch size
HELP;
return $help;
}
protected function doExecute()
{
if ($this->getOption('v')) {
$this->out('Executable: ' . $this->executable);
$this->out('Class: ' . __CLASS__);
$this->out('Arguments: ' . var_export($this->args, true));
$this->out('Options: ' . var_export($this->options, true));
}
if (count($this->args) == 0) {
$this->out($this->getHelp());
return -1;
}
switch ($this->args[0]) {
case 'list':
return $this->doList();
break;
case 'set':
return $this->doSet();
break;
case 'move':
return $this->doMove();
break;
}
$this->out(sprintf('Invalid action "%s"', $this->args[0]));
return -1;
}
protected function doList()
{
$rowfmt = ' %-3s | %-20s';
$current = StorageManager::getBackend();
$this->out(sprintf($rowfmt, 'Sel', 'Name'));
$this->out('-----------------------');
$isregisterd = false;
foreach (StorageManager::listBackends() as $name => $class) {
$issel = ' ';
if ($current === $class) {
$issel = '*';
$isregisterd = true;
};
$this->out(sprintf($rowfmt, $issel, $name));
}
if ($current === '') {
$this->out();
$this->out('This system is using legacy storage system');
}
if ($current !== '' && !$isregisterd) {
$this->out();
$this->out('The current storage class (' . $current . ') is not registered!');
}
return 0;
}
protected function doSet()
{
if (count($this->args) !== 2) {
throw new CommandArgsException('Invalid arguments');
}
$name = $this->args[1];
$class = StorageManager::getByName($name);
if ($class === '') {
$this->out($name . ' is not a registered backend.');
return -1;
}
if (!StorageManager::setBackend($class)) {
$this->out($class . ' is not a valid backend storage class.');
return -1;
}
return 0;
}
protected function doMove()
{
$tables = null;
if (count($this->args) < 1 || count($this->args) > 2) {
throw new CommandArgsException('Invalid arguments');
}
if (count($this->args) == 2) {
$table = strtolower($this->args[1]);
if (!in_array($table, ['photo', 'attach'])) {
throw new CommandArgsException('Invalid table');
}
$tables = [$table];
}
$current = StorageManager::getBackend();
$total = 0;
do {
$moved = StorageManager::move($current, $tables, $this->getOption('n', 5000));
if ($moved) {
$this->out(date('[Y-m-d H:i:s] ') . sprintf('Moved %d files', $moved));
}
$total += $moved;
} while ($moved);
$this->out(sprintf(date('[Y-m-d H:i:s] ') . 'Moved %d files total', $total));
}
}

View file

@ -1,6 +1,8 @@
<?php
namespace Friendica\Core\Console;
namespace Friendica\Console;
use Friendica\Core\Config\Configuration;
/**
* Tired of chasing typos and finding them after a commit.
@ -12,6 +14,11 @@ class Typo extends \Asika\SimpleConsole\Console
{
protected $helpOptions = ['h', 'help', '?'];
/**
* @var Configuration
*/
private $config;
protected function getHelp()
{
$help = <<<HELP
@ -29,6 +36,13 @@ HELP;
return $help;
}
public function __construct(Configuration $config, array $argv = null)
{
parent::__construct($argv);
$this->config = $config;
}
protected function doExecute()
{
if ($this->getOption('v')) {
@ -41,9 +55,7 @@ HELP;
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
}
$a = get_app();
$php_path = $a->getConfigValue('config', 'php_path', 'php');
$php_path = $this->config->get('config', 'php_path', 'php');
if ($this->getOption('v')) {
$this->out('Directory: src');
@ -57,6 +69,18 @@ HELP;
}
}
if ($this->getOption('v')) {
$this->out('Directory: tests');
}
$Iterator = new \RecursiveDirectoryIterator('tests');
foreach (new \RecursiveIteratorIterator($Iterator) as $file) {
if (substr($file, -4) === '.php') {
$this->checkFile($php_path, $file);
}
}
if ($this->getOption('v')) {
$this->out('Directory: mod');
}
@ -86,8 +110,6 @@ HELP;
$this->out('String files');
}
$this->checkFile($php_path, 'util/strings.php');
$files = glob('view/lang/*/strings.php');
$this->checkFiles($php_path, $files);

View file

@ -4,12 +4,12 @@
*/
namespace Friendica\Content;
use Friendica\Core\Addon;
use Friendica\Core\Hook;
use Friendica\Core\L10n;
use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Util\Network;
use Friendica\Util\Strings;
/**
* @brief ContactSelector class
@ -19,6 +19,8 @@ class ContactSelector
/**
* @param string $current current
* @param string $foreign_net network
* @return string
* @throws \Exception
*/
public static function profileAssign($current, $foreign_net)
{
@ -69,15 +71,49 @@ class ContactSelector
return $o;
}
/**
* @param string $profile Profile URL
* @return string Server URL
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private static function getServerURLForProfile($profile)
{
$server_url = '';
// Fetch the server url from the contact table
$contact = DBA::selectFirst('contact', ['baseurl'], ['uid' => 0, 'nurl' => Strings::normaliseLink($profile)]);
if (DBA::isResult($contact) && !empty($contact['baseurl'])) {
$server_url = Strings::normaliseLink($contact['baseurl']);
}
if (empty($server_url)) {
// Fetch the server url from the gcontact table
$gcontact = DBA::selectFirst('gcontact', ['server_url'], ['nurl' => Strings::normaliseLink($profile)]);
if (!empty($gcontact) && !empty($gcontact['server_url'])) {
$server_url = Strings::normaliseLink($gcontact['server_url']);
}
}
if (empty($server_url)) {
// Create the server url out of the profile url
$parts = parse_url($profile);
unset($parts['path']);
$server_url = Strings::normaliseLink(Network::unparseURL($parts));
}
return $server_url;
}
/**
* @param string $network network
* @param string $profile optional, default empty
* @return string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function networkToName($network, $profile = "")
{
$nets = [
Protocol::DFRN => L10n::t('Friendica'),
Protocol::DFRN => L10n::t('DFRN'),
Protocol::OSTATUS => L10n::t('OStatus'),
Protocol::FEED => L10n::t('RSS/Atom'),
Protocol::MAIL => L10n::t('Email'),
@ -95,24 +131,15 @@ class ContactSelector
Protocol::PNUT => L10n::t('pnut'),
];
Addon::callHooks('network_to_name', $nets);
Hook::callAll('network_to_name', $nets);
$search = array_keys($nets);
$replace = array_values($nets);
$networkname = str_replace($search, $replace, $network);
if ((in_array($network, [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) && ($profile != "")) {
// Create the server url out of the profile url
$parts = parse_url($profile);
unset($parts['path']);
$server_url = [normalise_link(Network::unparseURL($parts))];
// Fetch the server url
$gcontact = DBA::selectFirst('gcontact', ['server_url'], ['nurl' => normalise_link($profile)]);
if (!empty($gcontact) && !empty($gcontact['server_url'])) {
$server_url[] = normalise_link($gcontact['server_url']);
}
if ((in_array($network, Protocol::FEDERATED)) && ($profile != "")) {
$server_url = self::getServerURLForProfile($profile);
// Now query the GServer for the platform name
$gserver = DBA::selectFirst('gserver', ['platform', 'network'], ['nurl' => $server_url]);
@ -137,22 +164,92 @@ class ContactSelector
return $networkname;
}
/**
* @param string $network network
* @param string $profile optional, default empty
* @return string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function networkToIcon($network, $profile = "")
{
$nets = [
Protocol::DFRN => 'friendica',
Protocol::OSTATUS => 'gnu-social', // There is no generic OStatus icon
Protocol::FEED => 'rss',
Protocol::MAIL => 'file-text-o', /// @todo
Protocol::DIASPORA => 'diaspora',
Protocol::ZOT => 'hubzilla',
Protocol::LINKEDIN => 'linkedin',
Protocol::XMPP => 'xmpp',
Protocol::MYSPACE => 'file-text-o', /// @todo
Protocol::GPLUS => 'google-plus',
Protocol::PUMPIO => 'file-text-o', /// @todo
Protocol::TWITTER => 'twitter',
Protocol::DIASPORA2 => 'diaspora',
Protocol::STATUSNET => 'gnu-social',
Protocol::ACTIVITYPUB => 'activitypub',
Protocol::PNUT => 'file-text-o', /// @todo
];
$platform_icons = ['diaspora' => 'diaspora', 'friendica' => 'friendica', 'friendika' => 'friendica',
'GNU Social' => 'gnu-social', 'gnusocial' => 'gnu-social', 'hubzilla' => 'hubzilla',
'mastodon' => 'mastodon', 'peertube' => 'peertube', 'pixelfed' => 'pixelfed',
'pleroma' => 'pleroma', 'red' => 'hubzilla', 'redmatrix' => 'hubzilla',
'socialhome' => 'social-home', 'wordpress' => 'wordpress'];
$search = array_keys($nets);
$replace = array_values($nets);
$network_icon = str_replace($search, $replace, $network);
if ((in_array($network, Protocol::FEDERATED)) && ($profile != "")) {
$server_url = self::getServerURLForProfile($profile);
// Now query the GServer for the platform name
$gserver = DBA::selectFirst('gserver', ['platform'], ['nurl' => $server_url]);
if (DBA::isResult($gserver) && !empty($gserver['platform'])) {
$network_icon = $platform_icons[strtolower($gserver['platform'])] ?? $network_icon;
}
}
return $network_icon;
}
/**
* @param string $current optional, default empty
* @param string $suffix optionsl, default empty
* @return string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function gender($current = "", $suffix = "")
{
$o = '';
$select = ['', L10n::t('Male'), L10n::t('Female'), L10n::t('Currently Male'), L10n::t('Currently Female'), L10n::t('Mostly Male'), L10n::t('Mostly Female'), L10n::t('Transgender'), L10n::t('Intersex'), L10n::t('Transsexual'), L10n::t('Hermaphrodite'), L10n::t('Neuter'), L10n::t('Non-specific'), L10n::t('Other'), L10n::t('Undecided')];
$select = [
'' => L10n::t('No answer'),
'Male' => L10n::t('Male'),
'Female' => L10n::t('Female'),
'Currently Male' => L10n::t('Currently Male'),
'Currently Female' => L10n::t('Currently Female'),
'Mostly Male' => L10n::t('Mostly Male'),
'Mostly Female' => L10n::t('Mostly Female'),
'Transgender' => L10n::t('Transgender'),
'Intersex' => L10n::t('Intersex'),
'Transsexual' => L10n::t('Transsexual'),
'Hermaphrodite' => L10n::t('Hermaphrodite'),
'Neuter' => L10n::t('Neuter'),
'Non-specific' => L10n::t('Non-specific'),
'Other' => L10n::t('Other'),
'Undecided' => L10n::t('Undecided'),
];
Addon::callHooks('gender_selector', $select);
Hook::callAll('gender_selector', $select);
$o .= "<select name=\"gender$suffix\" id=\"gender-select$suffix\" size=\"1\" >";
foreach ($select as $selection) {
foreach ($select as $neutral => $selection) {
if ($selection !== 'NOTRANSLATION') {
$selected = (($selection == $current) ? ' selected="selected" ' : '');
$o .= "<option value=\"$selection\" $selected >$selection</option>";
$selected = (($neutral == $current) ? ' selected="selected" ' : '');
$o .= "<option value=\"$neutral\" $selected >$selection</option>";
}
}
$o .= '</select>';
@ -162,20 +259,36 @@ class ContactSelector
/**
* @param string $current optional, default empty
* @param string $suffix optionsl, default empty
* @return string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function sexualPreference($current = "", $suffix = "")
{
$o = '';
$select = ['', L10n::t('Males'), L10n::t('Females'), L10n::t('Gay'), L10n::t('Lesbian'), L10n::t('No Preference'), L10n::t('Bisexual'), L10n::t('Autosexual'), L10n::t('Abstinent'), L10n::t('Virgin'), L10n::t('Deviant'), L10n::t('Fetish'), L10n::t('Oodles'), L10n::t('Nonsexual')];
$select = [
'' => L10n::t('No answer'),
'Males' => L10n::t('Males'),
'Females' => L10n::t('Females'),
'Gay' => L10n::t('Gay'),
'Lesbian' => L10n::t('Lesbian'),
'No Preference' => L10n::t('No Preference'),
'Bisexual' => L10n::t('Bisexual'),
'Autosexual' => L10n::t('Autosexual'),
'Abstinent' => L10n::t('Abstinent'),
'Virgin' => L10n::t('Virgin'),
'Deviant' => L10n::t('Deviant'),
'Fetish' => L10n::t('Fetish'),
'Oodles' => L10n::t('Oodles'),
'Nonsexual' => L10n::t('Nonsexual'),
];
Addon::callHooks('sexpref_selector', $select);
Hook::callAll('sexpref_selector', $select);
$o .= "<select name=\"sexual$suffix\" id=\"sexual-select$suffix\" size=\"1\" >";
foreach ($select as $selection) {
foreach ($select as $neutral => $selection) {
if ($selection !== 'NOTRANSLATION') {
$selected = (($selection == $current) ? ' selected="selected" ' : '');
$o .= "<option value=\"$selection\" $selected >$selection</option>";
$selected = (($neutral == $current) ? ' selected="selected" ' : '');
$o .= "<option value=\"$neutral\" $selected >$selection</option>";
}
}
$o .= '</select>';
@ -184,19 +297,53 @@ class ContactSelector
/**
* @param string $current optional, default empty
* @return string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function maritalStatus($current = "")
{
$o = '';
$select = ['', L10n::t('Single'), L10n::t('Lonely'), L10n::t('Available'), L10n::t('Unavailable'), L10n::t('Has crush'), L10n::t('Infatuated'), L10n::t('Dating'), L10n::t('Unfaithful'), L10n::t('Sex Addict'), L10n::t('Friends'), L10n::t('Friends/Benefits'), L10n::t('Casual'), L10n::t('Engaged'), L10n::t('Married'), L10n::t('Imaginarily married'), L10n::t('Partners'), L10n::t('Cohabiting'), L10n::t('Common law'), L10n::t('Happy'), L10n::t('Not looking'), L10n::t('Swinger'), L10n::t('Betrayed'), L10n::t('Separated'), L10n::t('Unstable'), L10n::t('Divorced'), L10n::t('Imaginarily divorced'), L10n::t('Widowed'), L10n::t('Uncertain'), L10n::t('It\'s complicated'), L10n::t('Don\'t care'), L10n::t('Ask me')];
$select = [
'' => L10n::t('No answer'),
'Single' => L10n::t('Single'),
'Lonely' => L10n::t('Lonely'),
'In a relation' => L10n::t('In a relation'),
'Has crush' => L10n::t('Has crush'),
'Infatuated' => L10n::t('Infatuated'),
'Dating' => L10n::t('Dating'),
'Unfaithful' => L10n::t('Unfaithful'),
'Sex Addict' => L10n::t('Sex Addict'),
'Friends' => L10n::t('Friends'),
'Friends/Benefits' => L10n::t('Friends/Benefits'),
'Casual' => L10n::t('Casual'),
'Engaged' => L10n::t('Engaged'),
'Married' => L10n::t('Married'),
'Imaginarily married' => L10n::t('Imaginarily married'),
'Partners' => L10n::t('Partners'),
'Cohabiting' => L10n::t('Cohabiting'),
'Common law' => L10n::t('Common law'),
'Happy' => L10n::t('Happy'),
'Not looking' => L10n::t('Not looking'),
'Swinger' => L10n::t('Swinger'),
'Betrayed' => L10n::t('Betrayed'),
'Separated' => L10n::t('Separated'),
'Unstable' => L10n::t('Unstable'),
'Divorced' => L10n::t('Divorced'),
'Imaginarily divorced' => L10n::t('Imaginarily divorced'),
'Widowed' => L10n::t('Widowed'),
'Uncertain' => L10n::t('Uncertain'),
'It\'s complicated' => L10n::t('It\'s complicated'),
'Don\'t care' => L10n::t('Don\'t care'),
'Ask me' => L10n::t('Ask me'),
];
Addon::callHooks('marital_selector', $select);
Hook::callAll('marital_selector', $select);
$o .= '<select name="marital" id="marital-select" size="1" >';
foreach ($select as $selection) {
foreach ($select as $neutral => $selection) {
if ($selection !== 'NOTRANSLATION') {
$selected = (($selection == $current) ? ' selected="selected" ' : '');
$o .= "<option value=\"$selection\" $selected >$selection</option>";
$selected = (($neutral == $current) ? ' selected="selected" ' : '');
$o .= "<option value=\"$neutral\" $selected >$selection</option>";
}
}
$o .= '</select>';

View file

@ -5,8 +5,8 @@
*/
namespace Friendica\Content;
use Friendica\Core\Addon;
use Friendica\Core\Config;
use Friendica\Core\Hook;
use Friendica\Core\L10n;
use Friendica\Core\PConfig;
@ -18,8 +18,9 @@ class Feature
* @param integer $uid user id
* @param string $feature feature
* @return boolean
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function isEnabled($uid, $feature)
public static function isEnabled(int $uid, $feature)
{
$x = Config::get('feature_lock', $feature, false);
@ -36,7 +37,7 @@ class Feature
}
$arr = ['uid' => $uid, 'feature' => $feature, 'enabled' => $x];
Addon::callHooks('isEnabled', $arr);
Hook::callAll('isEnabled', $arr);
return($arr['enabled']);
}
@ -45,6 +46,7 @@ class Feature
*
* @param string $feature feature
* @return boolean
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private static function getDefault($feature)
{
@ -69,6 +71,7 @@ class Feature
* @param bool $filtered True removes any locked features
*
* @return array
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function get($filtered = true)
{
@ -81,29 +84,26 @@ class Feature
['multi_profiles', L10n::t('Multiple Profiles'), L10n::t('Ability to create multiple profiles'), false, Config::get('feature_lock', 'multi_profiles', false)],
['photo_location', L10n::t('Photo Location'), L10n::t("Photo metadata is normally stripped. This extracts the location \x28if present\x29 prior to stripping metadata and links it to a map."), false, Config::get('feature_lock', 'photo_location', false)],
['export_calendar', L10n::t('Export Public Calendar'), L10n::t('Ability for visitors to download the public calendar'), false, Config::get('feature_lock', 'export_calendar', false)],
['trending_tags', L10n::t('Trending Tags'), L10n::t('Show a community page widget with a list of the most popular tags in recent public posts.'), false, Config::get('feature_lock', 'trending_tags', false)],
],
// Post composition
'composition' => [
L10n::t('Post Composition Features'),
['preview', L10n::t('Post Preview'), L10n::t('Allow previewing posts and comments before publishing them'), false, Config::get('feature_lock', 'preview', false)],
['aclautomention', L10n::t('Auto-mention Forums'), L10n::t('Add/remove mention when a forum page is selected/deselected in ACL window.'), false, Config::get('feature_lock', 'aclautomention', false)],
['explicit_mentions', L10n::t('Explicit Mentions'), L10n::t('Add explicit mentions to comment box for manual control over who gets mentioned in replies.'), false, Config::get('feature_lock', 'explicit_mentions', false)],
],
// Network sidebar widgets
'widgets' => [
L10n::t('Network Sidebar'),
['archives', L10n::t('Archives'), L10n::t('Ability to select posts by date ranges'), false, Config::get('feature_lock', 'archives', false)],
['forumlist_widget', L10n::t('List Forums'), L10n::t('Enable widget to display the forums your are connected with'), true, Config::get('feature_lock', 'forumlist_widget', false)],
['groups', L10n::t('Group Filter'), L10n::t('Enable widget to display Network posts only from selected group'), false, Config::get('feature_lock', 'groups', false)],
['networks', L10n::t('Network Filter'), L10n::t('Enable widget to display Network posts only from selected network'), false, Config::get('feature_lock', 'networks', false)],
['savedsearch', L10n::t('Saved Searches'), L10n::t('Save search terms for re-use'), false, Config::get('feature_lock', 'savedsearch', false)],
['networks', L10n::t('Protocol Filter'), L10n::t('Enable widget to display Network posts only from selected protocols'), false, Config::get('feature_lock', 'networks', false)],
],
// Network tabs
'net_tabs' => [
L10n::t('Network Tabs'),
['personal_tab', L10n::t('Network Personal Tab'), L10n::t('Enable tab to display only Network posts that you\'ve interacted on'), false, Config::get('feature_lock', 'personal_tab', false)],
['new_tab', L10n::t('Network New Tab'), L10n::t("Enable tab to display only new Network posts \x28from the last 12 hours\x29"), false, Config::get('feature_lock', 'new_tab', false)],
['link_tab', L10n::t('Network Shared Links Tab'), L10n::t('Enable tab to display only Network posts with links in them'), false, Config::get('feature_lock', 'link_tab', false)],
],
@ -111,14 +111,7 @@ class Feature
// Item tools
'tools' => [
L10n::t('Post/Comment Tools'),
['multi_delete', L10n::t('Multiple Deletion'), L10n::t('Select and delete multiple posts/comments at once'), false, Config::get('feature_lock', 'multi_delete', false)],
['edit_posts', L10n::t('Edit Sent Posts'), L10n::t('Edit and correct posts and comments after sending'), false, Config::get('feature_lock', 'edit_posts', false)],
['commtag', L10n::t('Tagging'), L10n::t('Ability to tag existing posts'), false, Config::get('feature_lock', 'commtag', false)],
['categories', L10n::t('Post Categories'), L10n::t('Add categories to your posts'), false, Config::get('feature_lock', 'categories', false)],
['filing', L10n::t('Saved Folders'), L10n::t('Ability to file posts under folders'), false, Config::get('feature_lock', 'filing', false)],
['dislike', L10n::t('Dislike Posts'), L10n::t('Ability to dislike posts/comments'), false, Config::get('feature_lock', 'dislike', false)],
['star_posts', L10n::t('Star Posts'), L10n::t('Ability to mark special posts with a star indicator'), false, Config::get('feature_lock', 'star_posts', false)],
['ignore_posts', L10n::t('Mute Post Notifications'), L10n::t('Ability to mute notifications for a thread'), false, Config::get('feature_lock', 'ignore_posts', false)],
],
// Advanced Profile Settings
@ -151,7 +144,7 @@ class Feature
}
}
Addon::callHooks('get', $arr);
Hook::callAll('get', $arr);
return $arr;
}
}

View file

@ -6,15 +6,14 @@
namespace Friendica\Content;
use Friendica\Core\Protocol;
use Friendica\Content\Feature;
use Friendica\Content\Text\HTML;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Util\Proxy as ProxyUtils;
require_once 'include/dba.php';
/**
* @brief This class handles methods related to the forum functionality
*/
@ -29,11 +28,12 @@ class ForumManager
* @param boolean $showprivate Show private groups
*
* @return array
* 'url' => forum url
* 'name' => forum name
* 'id' => number of the key from the array
* 'micro' => contact photo in format micro
* 'thumb' => contact photo in format thumb
* 'url' => forum url
* 'name' => forum name
* 'id' => number of the key from the array
* 'micro' => contact photo in format micro
* 'thumb' => contact photo in format thumb
* @throws \Exception
*/
public static function getList($uid, $lastitem, $showhidden = true, $showprivate = false)
{
@ -43,7 +43,7 @@ class ForumManager
$params = ['order' => ['name']];
}
$condition_str = "`network` = ? AND `uid` = ? AND NOT `blocked` AND NOT `pending` AND NOT `archive` AND `success_update` > `failure_update` AND ";
$condition_str = "`network` IN (?, ?) AND `uid` = ? AND NOT `blocked` AND NOT `pending` AND NOT `archive` AND ";
if ($showprivate) {
$condition_str .= '(`forum` OR `prv`)';
@ -58,7 +58,7 @@ class ForumManager
$forumlist = [];
$fields = ['id', 'url', 'name', 'micro', 'thumb'];
$condition = [$condition_str, Protocol::DFRN, $uid];
$condition = [$condition_str, Protocol::DFRN, Protocol::ACTIVITYPUB, $uid];
$contacts = DBA::select('contact', $fields, $condition, $params);
if (!$contacts) {
return($forumlist);
@ -88,13 +88,11 @@ class ForumManager
* @param int $uid The ID of the User
* @param int $cid The contact id which is used to mark a forum as "selected"
* @return string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function widget($uid, $cid = 0)
{
if (! intval(Feature::isEnabled(local_user(), 'forumlist_widget'))) {
return;
}
$o = '';
//sort by last updated item
@ -107,11 +105,13 @@ class ForumManager
if (DBA::isResult($contacts)) {
$id = 0;
$entries = [];
foreach ($contacts as $contact) {
$selected = (($cid == $contact['id']) ? ' forum-selected' : '');
$entry = [
'url' => 'network?f=&cid=' . $contact['id'],
'url' => 'network?cid=' . $contact['id'],
'external_url' => Contact::magicLink($contact['url']),
'name' => $contact['name'],
'cid' => $contact['id'],
@ -122,9 +122,9 @@ class ForumManager
$entries[] = $entry;
}
$tpl = get_markup_template('widget_forumlist.tpl');
$tpl = Renderer::getMarkupTemplate('widget_forumlist.tpl');
$o .= replace_macros(
$o .= Renderer::replaceMacros(
$tpl,
[
'$title' => L10n::t('Forums'),
@ -147,6 +147,8 @@ class ForumManager
*
* @param int $uid The ID of the User
* @return string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function profileAdvanced($uid)
{
@ -168,7 +170,7 @@ class ForumManager
$total_shown = 0;
$forumlist = '';
foreach ($contacts as $contact) {
$forumlist .= micropro($contact, false, 'forumlist-profile-advanced');
$forumlist .= HTML::micropro($contact, true, 'forumlist-profile-advanced');
$total_shown ++;
if ($total_shown == $show_total) {
break;
@ -187,24 +189,24 @@ class ForumManager
* Count unread items of connected forums and private groups
*
* @return array
* 'id' => contact id
* 'name' => contact/forum name
* 'count' => counted unseen forum items
* 'id' => contact id
* 'name' => contact/forum name
* 'count' => counted unseen forum items
* @throws \Exception
*/
public static function countUnseenItems()
{
$r = q(
$stmtContacts = DBA::p(
"SELECT `contact`.`id`, `contact`.`name`, COUNT(*) AS `count` FROM `item`
INNER JOIN `contact` ON `item`.`contact-id` = `contact`.`id`
WHERE `item`.`uid` = %d AND `item`.`visible` AND NOT `item`.`deleted` AND `item`.`unseen`
WHERE `item`.`uid` = ? AND `item`.`visible` AND NOT `item`.`deleted` AND `item`.`unseen`
AND `contact`.`network`= 'dfrn' AND (`contact`.`forum` OR `contact`.`prv`)
AND NOT `contact`.`blocked` AND NOT `contact`.`hidden`
AND NOT `contact`.`pending` AND NOT `contact`.`archive`
AND `contact`.`success_update` > `failure_update`
GROUP BY `contact`.`id` ",
intval(local_user())
local_user()
);
return $r;
return DBA::toArray($stmtContacts);
}
}

View file

@ -5,17 +5,15 @@
namespace Friendica\Content;
use Friendica\App;
use Friendica\Content\Feature;
use Friendica\Core\Addon;
use Friendica\Core\Config;
use Friendica\Core\Hook;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
use Friendica\Core\Session;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Model\Profile;
require_once 'boot.php';
require_once 'include/text.php';
use Friendica\Model\User;
class Nav
{
@ -31,7 +29,7 @@ class Nav
'directory' => null,
'settings' => null,
'contacts' => null,
'manage' => null,
'delegation'=> null,
'events' => null,
'register' => null
];
@ -45,6 +43,8 @@ class Nav
/**
* Set a menu item in navbar as selected
*
* @param string $item
*/
public static function setSelected($item)
{
@ -53,18 +53,21 @@ class Nav
/**
* Build page header and site navigation bars
*
* @param App $a
* @return string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function build(App $a)
{
// Placeholder div for popup panel
$nav = '<div id="panel" style="display: none;"></div>' ;
$nav = '<div id="panel" style="display: none;"></div>';
$nav_info = self::getInfo($a);
$tpl = get_markup_template('nav.tpl');
$tpl = Renderer::getMarkupTemplate('nav.tpl');
$nav .= replace_macros($tpl, [
'$baseurl' => System::baseUrl(),
$nav .= Renderer::replaceMacros($tpl, [
'$sitelocation' => $nav_info['sitelocation'],
'$nav' => $nav_info['nav'],
'$banner' => $nav_info['banner'],
@ -76,7 +79,7 @@ class Nav
'$search_hint' => L10n::t('@name, !forum, #tags, content')
]);
Addon::callHooks('page_header', $nav);
Hook::callAll('page_header', $nav);
return $nav;
}
@ -107,7 +110,7 @@ class Nav
if (local_user() || !$privateapps) {
$arr = ['app_menu' => self::$app_menu];
Addon::callHooks('app_menu', $arr);
Hook::callAll('app_menu', $arr);
self::$app_menu = $arr['app_menu'];
}
@ -117,12 +120,13 @@ class Nav
* Prepares a list of navigation links
*
* @brief Prepares a list of navigation links
* @param App $a
* @param App $a
* @return array Navigation links
* string 'sitelocation' => The webbie (username@site.com)
* array 'nav' => Array of links used in the nav menu
* string 'banner' => Formatted html link with banner image
* array 'userinfo' => Array of user information (name, icon)
* string 'sitelocation' => The webbie (username@site.com)
* array 'nav' => Array of links used in the nav menu
* string 'banner' => Formatted html link with banner image
* array 'userinfo' => Array of user information (name, icon)
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private static function getInfo(App $a)
{
@ -145,9 +149,13 @@ class Nav
$nav['usermenu'] = [];
$userinfo = null;
if (local_user()) {
if (Session::isAuthenticated()) {
$nav['logout'] = ['logout', L10n::t('Logout'), '', L10n::t('End this session')];
} else {
$nav['login'] = ['login', L10n::t('Login'), ($a->module == 'login' ? 'selected' : ''), L10n::t('Sign in')];
}
if (local_user()) {
// user menu
$nav['usermenu'][] = ['profile/' . $a->user['nickname'], L10n::t('Status'), '', L10n::t('Your posts and conversations')];
$nav['usermenu'][] = ['profile/' . $a->user['nickname'] . '?tab=profile', L10n::t('Profile'), '', L10n::t('Your profile page')];
@ -162,21 +170,19 @@ class Nav
'icon' => (DBA::isResult($contact) ? $a->removeBaseURL($contact['micro']) : 'images/person-48.jpg'),
'name' => $a->user['username'],
];
} else {
$nav['login'] = ['login', L10n::t('Login'), ($a->module == 'login' ? 'selected' : ''), L10n::t('Sign in')];
}
// "Home" should also take you home from an authenticated remote profile connection
$homelink = Profile::getMyURL();
if (! $homelink) {
$homelink = ((x($_SESSION, 'visitor_home')) ? $_SESSION['visitor_home'] : '');
$homelink = Session::get('visitor_home', '');
}
if (($a->module != 'home') && (! (local_user()))) {
$nav['home'] = [$homelink, L10n::t('Home'), '', L10n::t('Home Page')];
}
if (intval(Config::get('config', 'register_policy')) === REGISTER_OPEN && !local_user() && !remote_user()) {
if (intval(Config::get('config', 'register_policy')) === \Friendica\Module\Register::OPEN && !Session::isAuthenticated()) {
$nav['register'] = ['register', L10n::t('Register'), '', L10n::t('Create an account')];
}
@ -233,12 +239,12 @@ class Nav
// The following nav links are only show to logged in users
if (local_user()) {
$nav['network'] = ['network', L10n::t('Network'), '', L10n::t('Conversations from your friends')];
$nav['net_reset'] = ['network/0?f=&order=comment&nets=all', L10n::t('Network Reset'), '', L10n::t('Load Network page with no filters')];
$nav['net_reset'] = ['network/?f=', L10n::t('Network Reset'), '', L10n::t('Load Network page with no filters')];
$nav['home'] = ['profile/' . $a->user['nickname'], L10n::t('Home'), '', L10n::t('Your posts and conversations')];
// Don't show notifications for public communities
if (defaults($_SESSION, 'page_flags', '') != Contact::PAGE_COMMUNITY) {
if (Session::get('page_flags', '') != User::PAGE_FLAGS_COMMUNITY) {
$nav['introductions'] = ['notifications/intros', L10n::t('Introductions'), '', L10n::t('Friend Requests')];
$nav['notifications'] = ['notifications', L10n::t('Notifications'), '', L10n::t('Notifications')];
$nav['notifications']['all'] = ['notifications/system', L10n::t('See all notifications'), '', ''];
@ -251,11 +257,9 @@ class Nav
$nav['messages']['new'] = ['message/new', L10n::t('New Message'), '', L10n::t('New Message')];
if (is_array($a->identities) && count($a->identities) > 1) {
$nav['manage'] = ['manage', L10n::t('Manage'), '', L10n::t('Manage other pages')];
$nav['delegation'] = ['delegation', L10n::t('Delegation'), '', L10n::t('Manage other pages')];
}
$nav['delegations'] = ['delegate', L10n::t('Delegations'), '', L10n::t('Delegate Page Management')];
$nav['settings'] = ['settings', L10n::t('Settings'), '', L10n::t('Account settings')];
if (Feature::isEnabled(local_user(), 'multi_profiles')) {
@ -278,7 +282,7 @@ class Nav
$banner = '<a href="https://friendi.ca"><img id="logo-img" src="images/friendica-32.png" alt="logo" /></a><span id="logo-text"><a href="https://friendi.ca">Friendica</a></span>';
}
Addon::callHooks('nav_info', $nav);
Hook::callAll('nav_info', $nav);
return [
'sitelocation' => $sitelocation,

View file

@ -10,18 +10,18 @@ use DOMNode;
use DOMText;
use DOMXPath;
use Exception;
use Friendica\Core\Addon;
use Friendica\Core\Cache;
use Friendica\Core\Config;
use Friendica\Core\Hook;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Network;
use Friendica\Util\ParseUrl;
use Friendica\Util\Proxy as ProxyUtils;
require_once 'include/dba.php';
use Friendica\Util\Strings;
/**
* Handles all OEmbed content fetching and replacement
@ -51,16 +51,17 @@ class OEmbed
* @param bool $no_rich_type If set to true rich type content won't be fetched.
*
* @return \Friendica\Object\OEmbed
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function fetchURL($embedurl, $no_rich_type = false)
{
$embedurl = trim($embedurl, '\'"');
$a = get_app();
$a = \get_app();
$cache_key = 'oembed:' . $a->videowidth . ':' . $embedurl;
$condition = ['url' => normalise_link($embedurl), 'maxwidth' => $a->videowidth];
$condition = ['url' => Strings::normaliseLink($embedurl), 'maxwidth' => $a->videowidth];
$oembed_record = DBA::selectFirst('oembed', ['content'], $condition);
if (DBA::isResult($oembed_record)) {
$json_string = $oembed_record['content'];
@ -82,8 +83,7 @@ class OEmbed
if (!in_array($ext, $noexts)) {
// try oembed autodiscovery
$redirects = 0;
$html_text = Network::fetchUrl($embedurl, false, $redirects, 15, 'text/*');
$html_text = Network::fetchUrl($embedurl, false, 15, 'text/*');
if ($html_text) {
$dom = @DOMDocument::loadHTML($html_text);
if ($dom) {
@ -115,7 +115,7 @@ class OEmbed
if (!empty($oembed->type) && $oembed->type != 'error') {
DBA::insert('oembed', [
'url' => normalise_link($embedurl),
'url' => Strings::normaliseLink($embedurl),
'maxwidth' => $a->videowidth,
'content' => $json_string,
'created' => DateTimeFormat::utcNow()
@ -159,7 +159,7 @@ class OEmbed
}
}
Addon::callHooks('oembed_fetch_url', $embedurl, $oembed);
Hook::callAll('oembed_fetch_url', $embedurl, $oembed);
return $oembed;
}
@ -178,9 +178,8 @@ class OEmbed
$th = 120;
$tw = $th * $tr;
$tpl = get_markup_template('oembed_video.tpl');
$ret .= replace_macros($tpl, [
'$baseurl' => System::baseUrl(),
$tpl = Renderer::getMarkupTemplate('oembed_video.tpl');
$ret .= Renderer::replaceMacros($tpl, [
'$embedurl' => $oembed->embed_url,
'$escapedhtml' => base64_encode($oembed->html),
'$tw' => $tw,
@ -245,8 +244,7 @@ class OEmbed
$ret .= '</div>';
$ret = str_replace("\n", "", $ret);
return mb_convert_encoding($ret, 'HTML-ENTITIES', mb_detect_encoding($ret));
return str_replace("\n", "", $ret);
}
public static function BBCode2HTML($text)
@ -261,6 +259,9 @@ class OEmbed
/**
* Find <span class='oembed'>..<a href='url' rel='oembed'>..</a></span>
* and replace it with [embed]url[/embed]
*
* @param $text
* @return string
*/
public static function HTML2BBCode($text)
{
@ -299,6 +300,7 @@ class OEmbed
* @brief Determines if rich content OEmbed is allowed for the provided URL
* @param string $url
* @return boolean
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function isAllowedURL($url)
{
@ -307,12 +309,12 @@ class OEmbed
}
$domain = parse_url($url, PHP_URL_HOST);
if (!x($domain)) {
if (empty($domain)) {
return false;
}
$str_allowed = Config::get('system', 'allowed_oembed', '');
if (!x($str_allowed)) {
if (empty($str_allowed)) {
return false;
}
@ -333,7 +335,7 @@ class OEmbed
throw new Exception('OEmbed failed for URL: ' . $url);
}
if (x($title)) {
if (!empty($title)) {
$o->title = $title;
}
@ -354,25 +356,24 @@ class OEmbed
* Since the iframe is automatically resized on load, there are no need for ugly
* and impractical scrollbars.
*
* @todo This function is currently unused until someone™ adds support for a separate OEmbed domain
* @todo This function is currently unused until someone™ adds support for a separate OEmbed domain
*
* @param string $src Original remote URL to embed
* @param string $width
* @param string $height
* @return string formatted HTML
*
* @see oembed_format_object()
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @see oembed_format_object()
*/
private static function iframe($src, $width, $height)
{
$a = get_app();
if (!$height || strstr($height, '%')) {
$height = '200';
}
$width = '100%';
$src = System::baseUrl() . '/oembed/' . base64url_encode($src);
$src = System::baseUrl() . '/oembed/' . Strings::base64UrlEncode($src);
return '<iframe onload="resizeIframe(this);" class="embed_rich" height="' . $height . '" width="' . $width . '" src="' . $src . '" allowfullscreen scrolling="no" frameborder="no">' . L10n::t('Embedded content') . '</iframe>';
}

View file

@ -3,6 +3,8 @@
namespace Friendica\Content;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
use Friendica\Util\Strings;
/**
* The Pager has two very different output, Minimal and Full, see renderMinimal() and renderFull() for more details.
@ -63,7 +65,7 @@ class Pager
/**
* Returns the current page number
*
* @return type
* @return int
*/
public function getPage()
{
@ -81,7 +83,7 @@ class Pager
*/
public function getBaseQueryString()
{
return $this->baseQueryString;
return Strings::ensureQueryParameter($this->baseQueryString);
}
/**
@ -121,21 +123,6 @@ class Pager
$this->baseQueryString = $stripped;
}
/**
* Ensures the provided URI has its query string punctuation in order.
*
* @param string $uri
* @return string
*/
private function ensureQueryParameter($uri)
{
if (strpos($uri, '?') === false && ($pos = strpos($uri, '&')) !== false) {
$uri = substr($uri, 0, $pos) . '?' . substr($uri, $pos + 1);
}
return $uri;
}
/**
* @brief Minimal pager (newer/older)
*
@ -153,6 +140,7 @@ class Pager
*
* @param integer $itemCount The number of displayed items on the page
* @return string HTML string of the pager
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public function renderMinimal($itemCount)
{
@ -161,19 +149,19 @@ class Pager
$data = [
'class' => 'pager',
'prev' => [
'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=' . ($this->getPage() - 1)),
'url' => Strings::ensureQueryParameter($this->baseQueryString . '&page=' . ($this->getPage() - 1)),
'text' => L10n::t('newer'),
'class' => 'previous' . ($this->getPage() == 1 ? ' disabled' : '')
],
'next' => [
'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=' . ($this->getPage() + 1)),
'url' => Strings::ensureQueryParameter($this->baseQueryString . '&page=' . ($this->getPage() + 1)),
'text' => L10n::t('older'),
'class' => 'next' . ($displayedItemCount <= 0 ? ' disabled' : '')
'class' => 'next' . ($displayedItemCount < $this->getItemsPerPage() ? ' disabled' : '')
]
];
$tpl = get_markup_template('paginate.tpl');
return replace_macros($tpl, ['pager' => $data]);
$tpl = Renderer::getMarkupTemplate('paginate.tpl');
return Renderer::replaceMacros($tpl, ['pager' => $data]);
}
/**
@ -195,6 +183,7 @@ class Pager
*
* @param integer $itemCount The total number of items including those note displayed on the page
* @return string HTML string of the pager
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public function renderFull($itemCount)
{
@ -205,12 +194,12 @@ class Pager
$data['class'] = 'pagination';
if ($totalItemCount > $this->getItemsPerPage()) {
$data['first'] = [
'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=1'),
'url' => Strings::ensureQueryParameter($this->baseQueryString . '&page=1'),
'text' => L10n::t('first'),
'class' => $this->getPage() == 1 ? 'disabled' : ''
];
$data['prev'] = [
'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=' . ($this->getPage() - 1)),
'url' => Strings::ensureQueryParameter($this->baseQueryString . '&page=' . ($this->getPage() - 1)),
'text' => L10n::t('prev'),
'class' => $this->getPage() == 1 ? 'disabled' : ''
];
@ -237,7 +226,7 @@ class Pager
];
} else {
$pages[$i] = [
'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=' . $i),
'url' => Strings::ensureQueryParameter($this->baseQueryString . '&page=' . $i),
'text' => $i,
'class' => 'n'
];
@ -253,7 +242,7 @@ class Pager
];
} else {
$pages[$i] = [
'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=' . $i),
'url' => Strings::ensureQueryParameter($this->baseQueryString . '&page=' . $i),
'text' => $i,
'class' => 'n'
];
@ -265,18 +254,18 @@ class Pager
$lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages);
$data['next'] = [
'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=' . ($this->getPage() + 1)),
'url' => Strings::ensureQueryParameter($this->baseQueryString . '&page=' . ($this->getPage() + 1)),
'text' => L10n::t('next'),
'class' => $this->getPage() == $lastpage ? 'disabled' : ''
];
$data['last'] = [
'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=' . $lastpage),
'url' => Strings::ensureQueryParameter($this->baseQueryString . '&page=' . $lastpage),
'text' => L10n::t('last'),
'class' => $this->getPage() == $lastpage ? 'disabled' : ''
];
}
$tpl = get_markup_template('paginate.tpl');
return replace_macros($tpl, ['pager' => $data]);
$tpl = Renderer::getMarkupTemplate('paginate.tpl');
return Renderer::replaceMacros($tpl, ['pager' => $data]);
}
}

View file

@ -14,11 +14,11 @@
*/
namespace Friendica\Content;
use Friendica\App;
use Friendica\Core\Addon;
use Friendica\Core\Config;
use Friendica\Core\Hook;
use Friendica\Core\PConfig;
use Friendica\Core\System;
use Friendica\Util\Strings;
/**
* This class contains functions to handle smiles
@ -55,10 +55,11 @@ class Smilies
* Get an array of all smilies, both internal and from addons.
*
* @return array
* 'texts' => smilie shortcut
* 'icons' => icon in html
* 'texts' => smilie shortcut
* 'icons' => icon in html
*
* @hook smilie ('texts' => smilies texts array, 'icons' => smilies html array)
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @hook smilie ('texts' => smilies texts array, 'icons' => smilies html array)
*/
public static function getList()
{
@ -140,7 +141,7 @@ class Smilies
];
$params = ['texts' => $texts, 'icons' => $icons];
Addon::callHooks('smilie', $params);
Hook::callAll('smilie', $params);
return $params;
}
@ -182,6 +183,7 @@ class Smilies
* @param boolean $no_images Only replace emoticons without images
*
* @return string HTML Output of the Smilie
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function replace($s, $no_images = false)
{
@ -201,6 +203,7 @@ class Smilies
* @param array $smilies An string replacement array with the following structure: ['texts' => [], 'icons' => []]
* @param bool $no_images Only replace shortcodes without image replacement (e.g. Unicode characters)
* @return string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function replaceFromArray($text, array $smilies, $no_images = false)
{
@ -210,8 +213,8 @@ class Smilies
return $text;
}
$text = preg_replace_callback('/<pre>(.*?)<\/pre>/ism' , 'self::encode', $text);
$text = preg_replace_callback('/<code>(.*?)<\/code>/ism', 'self::encode', $text);
$text = preg_replace_callback('/<(pre)>(.*?)<\/pre>/ism', 'self::encode', $text);
$text = preg_replace_callback('/<(code)>(.*?)<\/code>/ism', 'self::encode', $text);
if ($no_images) {
$cleaned = ['texts' => [], 'icons' => []];
@ -228,8 +231,8 @@ class Smilies
$text = preg_replace_callback('/&lt;(3+)/', 'self::pregHeart', $text);
$text = self::strOrigReplace($smilies['texts'], $smilies['icons'], $text);
$text = preg_replace_callback('/<pre>(.*?)<\/pre>/ism', 'self::decode', $text);
$text = preg_replace_callback('/<code>(.*?)<\/code>/ism', 'self::decode', $text);
$text = preg_replace_callback('/<(code)>(.*?)<\/code>/ism', 'self::decode', $text);
$text = preg_replace_callback('/<(pre)>(.*?)<\/pre>/ism', 'self::decode', $text);
return $text;
}
@ -241,17 +244,18 @@ class Smilies
*/
private static function encode($m)
{
return(str_replace($m[1], base64url_encode($m[1]), $m[0]));
return '<' . $m[1] . '>' . Strings::base64UrlEncode($m[2]) . '</' . $m[1] . '>';
}
/**
* @param string $m string
*
* @return string base64 decoded string
* @throws \Exception
*/
private static function decode($m)
{
return(str_replace($m[1], base64url_decode($m[1]), $m[0]));
return '<' . $m[1] . '>' . Strings::base64UrlDecode($m[2]) . '</' . $m[1] . '>';
}
@ -262,17 +266,19 @@ class Smilies
*
* @return string HTML Output
*
* @todo: Rework because it doesn't work correctly
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private static function pregHeart($x)
{
if (strlen($x[1]) == 1) {
return $x[0];
}
$t = '';
for ($cnt = 0; $cnt < strlen($x[1]); $cnt ++) {
$t .= '<img class="smiley" src="' . System::baseUrl() . '/images/smiley-heart.gif" alt="&lt;3" />';
$t .= '';
}
$r = str_replace($x[0], $t, $x[0]);
return $r;
}

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,4 @@
<?php
/**
* @file src/Content/Text/HTML.php
*/
@ -8,8 +7,15 @@ namespace Friendica\Content\Text;
use DOMDocument;
use DOMXPath;
use Friendica\Core\Addon;
use Friendica\Content\Widget\ContactBlock;
use Friendica\Core\Hook;
use Friendica\Core\L10n;
use Friendica\Core\Config;
use Friendica\Core\Renderer;
use Friendica\Model\Contact;
use Friendica\Util\Network;
use Friendica\Util\Proxy as ProxyUtils;
use Friendica\Util\Strings;
use Friendica\Util\XML;
use League\HTMLToMarkdown\HtmlConverter;
@ -36,20 +42,39 @@ class HTML
return $cleaned;
}
private static function tagToBBCode(DOMDocument $doc, $tag, $attributes, $startbb, $endbb)
/**
* Search all instances of a specific HTML tag node in the provided DOM document and replaces them with BBCode text nodes.
*
* @see HTML::tagToBBCodeSub()
*/
private static function tagToBBCode(DOMDocument $doc, string $tag, array $attributes, string $startbb, string $endbb, bool $ignoreChildren = false)
{
do {
$done = self::tagToBBCodeSub($doc, $tag, $attributes, $startbb, $endbb);
$done = self::tagToBBCodeSub($doc, $tag, $attributes, $startbb, $endbb, $ignoreChildren);
} while ($done);
}
private static function tagToBBCodeSub(DOMDocument $doc, $tag, $attributes, $startbb, $endbb)
/**
* Search the first specific HTML tag node in the provided DOM document and replaces it with BBCode text nodes.
*
* @param DOMDocument $doc
* @param string $tag HTML tag name
* @param array $attributes Array of attributes to match and optionally use the value from
* @param string $startbb BBCode tag opening
* @param string $endbb BBCode tag closing
* @param bool $ignoreChildren If set to false, the HTML tag children will be appended as text inside the BBCode tag
* Otherwise, they will be entirely ignored. Useful for simple BBCode that draw their
* inner value from an attribute value and disregard the tag children.
* @return bool Whether a replacement was done
*/
private static function tagToBBCodeSub(DOMDocument $doc, string $tag, array $attributes, string $startbb, string $endbb, bool $ignoreChildren = false)
{
$savestart = str_replace('$', '\x01', $startbb);
$replace = false;
$xpath = new DOMXPath($doc);
/** @var \DOMNode[] $list */
$list = $xpath->query("//" . $tag);
foreach ($list as $node) {
$attr = [];
@ -91,10 +116,14 @@ class HTML
$node->parentNode->insertBefore($StartCode, $node);
if ($node->hasChildNodes()) {
foreach ($node->childNodes as $child) {
$newNode = $child->cloneNode(true);
$node->parentNode->insertBefore($newNode, $node);
if (!$ignoreChildren && $node->hasChildNodes()) {
/** @var \DOMNode $child */
foreach ($node->childNodes as $key => $child) {
/* Remove empty text nodes at the start or at the end of the children list */
if ($key > 0 && $key < $node->childNodes->length - 1 || $child->nodeName != '#text' || trim($child->nodeValue)) {
$newNode = $child->cloneNode(true);
$node->parentNode->insertBefore($newNode, $node);
}
}
}
@ -109,12 +138,13 @@ class HTML
/**
* Made by: ike@piratenpartei.de
* Originally made for the syncom project: http://wiki.piratenpartei.de/Syncom
* https://github.com/annando/Syncom
* https://github.com/annando/Syncom
*
* @brief Converter for HTML to BBCode
* @param string $message
* @param string $basepath
* @return string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function toBBCode($message, $basepath = '')
{
@ -140,12 +170,14 @@ class HTML
$message = str_replace(
[
"<li><p>",
"</p></li>",
], [
"<li>",
"</li>",
], $message
"<li><p>",
"</p></li>",
],
[
"<li>",
"</li>",
],
$message
);
// remove namespaces
@ -157,7 +189,7 @@ class HTML
$message = mb_convert_encoding($message, 'HTML-ENTITIES', "UTF-8");
@$doc->loadHTML($message);
@$doc->loadHTML($message, LIBXML_HTML_NODEFDTD);
XML::deleteNode($doc, 'style');
XML::deleteNode($doc, 'head');
@ -169,13 +201,16 @@ class HTML
$xpath = new DomXPath($doc);
$list = $xpath->query("//pre");
foreach ($list as $node) {
$node->nodeValue = str_replace("\n", "\r", $node->nodeValue);
// Ensure to escape unescaped & - they will otherwise raise a warning
$safe_value = preg_replace('/&(?!\w+;)/', '&amp;', $node->nodeValue);
$node->nodeValue = str_replace("\n", "\r", $safe_value);
}
$message = $doc->saveHTML();
$message = str_replace(["\n<", ">\n", "\r", "\n", "\xC3\x82\xC2\xA0"], ["<", ">", "<br />", " ", ""], $message);
$message = preg_replace('= [\s]*=i', " ", $message);
@$doc->loadHTML($message);
@$doc->loadHTML($message, LIBXML_HTML_NODEFDTD);
self::tagToBBCode($doc, 'html', [], "", "");
self::tagToBBCode($doc, 'body', [], "", "");
@ -184,8 +219,13 @@ class HTML
self::tagToBBCode($doc, 'p', ['class' => 'MsoNormal', 'style' => 'margin-left:35.4pt'], '[quote]', '[/quote]');
// Outlook-Quote - Variant 2
self::tagToBBCode($doc, 'div', ['style' => 'border:none;border-left:solid blue 1.5pt;padding:0cm 0cm 0cm 4.0pt'],
'[quote]', '[/quote]');
self::tagToBBCode(
$doc,
'div',
['style' => 'border:none;border-left:solid blue 1.5pt;padding:0cm 0cm 0cm 4.0pt'],
'[quote]',
'[/quote]'
);
// MyBB-Stuff
self::tagToBBCode($doc, 'span', ['style' => 'text-decoration: underline;'], '[u]', '[/u]');
@ -274,14 +314,14 @@ class HTML
self::tagToBBCode($doc, 'a', ['href' => '/mailto:(.+)/'], '[mail=$1]', '[/mail]');
self::tagToBBCode($doc, 'a', ['href' => '/(.+)/'], '[url=$1]', '[/url]');
self::tagToBBCode($doc, 'img', ['src' => '/(.+)/', 'width' => '/(\d+)/', 'height' => '/(\d+)/'], '[img=$2x$3]$1',
'[/img]');
self::tagToBBCode($doc, 'img', ['src' => '/(.+)/'], '[img]$1', '[/img]');
self::tagToBBCode($doc, 'img', ['src' => '/(.+)/', 'alt' => '/(.+)/'], '[img=$1]$2', '[/img]', true);
self::tagToBBCode($doc, 'img', ['src' => '/(.+)/', 'width' => '/(\d+)/', 'height' => '/(\d+)/'], '[img=$2x$3]$1', '[/img]', true);
self::tagToBBCode($doc, 'img', ['src' => '/(.+)/'], '[img]$1', '[/img]', true);
self::tagToBBCode($doc, 'video', ['src' => '/(.+)/'], '[video]$1', '[/video]');
self::tagToBBCode($doc, 'audio', ['src' => '/(.+)/'], '[audio]$1', '[/audio]');
self::tagToBBCode($doc, 'iframe', ['src' => '/(.+)/'], '[iframe]$1', '[/iframe]');
self::tagToBBCode($doc, 'video', ['src' => '/(.+)/'], '[video]$1', '[/video]', true);
self::tagToBBCode($doc, 'audio', ['src' => '/(.+)/'], '[audio]$1', '[/audio]', true);
self::tagToBBCode($doc, 'iframe', ['src' => '/(.+)/'], '[iframe]$1', '[/iframe]', true);
self::tagToBBCode($doc, 'key', [], '[code]', '[/code]');
self::tagToBBCode($doc, 'code', [], '[code]', '[/code]');
@ -298,7 +338,7 @@ class HTML
$message = preg_replace('=\r *\r=i', "\n", $message);
$message = str_replace("\r", "\n", $message);
Addon::callHooks('html2bbcode', $message);
Hook::callAll('html2bbcode', $message);
$message = strip_tags($message);
@ -344,12 +384,15 @@ class HTML
"[/",
"[list]",
"[list=1]",
"[*]"], $message
"[*]"],
$message
);
} while ($message != $oldmessage);
$message = str_replace(
['[b][b]', '[/b][/b]', '[i][i]', '[/i][/i]'], ['[b]', '[/b]', '[i]', '[/i]'], $message
['[b][b]', '[/b][/b]', '[i][i]', '[/i][/i]'],
['[b]', '[/b]', '[i]', '[/i]'],
$message
);
// Handling Yahoo style of mails
@ -394,6 +437,10 @@ class HTML
$link = $matches[0];
$url = $matches[1];
if (empty($url) || empty(parse_url($url))) {
return $matches[0];
}
$parts = array_merge($base, parse_url($url));
$url2 = Network::unparseURL($parts);
@ -424,7 +471,8 @@ class HTML
foreach ($matches as $match) {
$body = preg_replace_callback(
$match, function ($match) use ($basepath) {
$match,
function ($match) use ($basepath) {
return self::qualifyURLsSub($match, $basepath);
},
$body
@ -540,6 +588,8 @@ class HTML
$ignore = false;
}
$ignore = $ignore || strpos($treffer[1], '#') === 0;
if (!$ignore) {
$urls[$treffer[1]] = $treffer[1];
}
@ -548,7 +598,13 @@ class HTML
return $urls;
}
public static function toPlaintext($html, $wraplength = 75, $compact = false)
/**
* @param string $html
* @param int $wraplength Ensures individual lines aren't longer than this many characters. Doesn't break words.
* @param bool $compact True: Completely strips image tags; False: Keeps image URLs
* @return string
*/
public static function toPlaintext(string $html, $wraplength = 75, $compact = false)
{
$message = str_replace("\r", "", $html);
@ -557,38 +613,20 @@ class HTML
$message = mb_convert_encoding($message, 'HTML-ENTITIES', "UTF-8");
@$doc->loadHTML($message);
$xpath = new DOMXPath($doc);
$list = $xpath->query("//pre");
foreach ($list as $node) {
$node->nodeValue = str_replace("\n", "\r", $node->nodeValue);
}
@$doc->loadHTML($message, LIBXML_HTML_NODEFDTD);
$message = $doc->saveHTML();
$message = str_replace(["\n<", ">\n", "\r", "\n", "\xC3\x82\xC2\xA0"], ["<", ">", "<br>", " ", ""], $message);
$message = preg_replace('= [\s]*=i', " ", $message);
// Remove eventual UTF-8 BOM
$message = str_replace("\xC3\x82\xC2\xA0", "", $message);
// Collecting all links
$urls = self::collectURLs($message);
@$doc->loadHTML($message);
@$doc->loadHTML($message, LIBXML_HTML_NODEFDTD);
self::tagToBBCode($doc, 'html', [], '', '');
self::tagToBBCode($doc, 'body', [], '', '');
// MyBB-Auszeichnungen
/*
self::node2BBCode($doc, 'span', array('style'=>'text-decoration: underline;'), '_', '_');
self::node2BBCode($doc, 'span', array('style'=>'font-style: italic;'), '/', '/');
self::node2BBCode($doc, 'span', array('style'=>'font-weight: bold;'), '*', '*');
self::node2BBCode($doc, 'strong', array(), '*', '*');
self::node2BBCode($doc, 'b', array(), '*', '*');
self::node2BBCode($doc, 'i', array(), '/', '/');
self::node2BBCode($doc, 'u', array(), '_', '_');
*/
if ($compact) {
self::tagToBBCode($doc, 'blockquote', [], "»", "«");
} else {
@ -602,8 +640,6 @@ class HTML
self::tagToBBCode($doc, 'div', [], "\r", "\r");
self::tagToBBCode($doc, 'p', [], "\n", "\n");
//self::node2BBCode($doc, 'ul', array(), "\n[list]", "[/list]\n");
//self::node2BBCode($doc, 'ol', array(), "\n[list=1]", "[/list]\n");
self::tagToBBCode($doc, 'li', [], "\n* ", "\n");
self::tagToBBCode($doc, 'hr', [], "\n" . str_repeat("-", 70) . "\n", "");
@ -618,12 +654,6 @@ class HTML
self::tagToBBCode($doc, 'h5', [], "\n\n*", "*\n");
self::tagToBBCode($doc, 'h6', [], "\n\n*", "*\n");
// Problem: there is no reliable way to detect if it is a link to a tag or profile
//self::node2BBCode($doc, 'a', array('href'=>'/(.+)/'), ' $1 ', ' ', true);
//self::node2BBCode($doc, 'a', array('href'=>'/(.+)/', 'rel'=>'oembed'), ' $1 ', '', true);
//self::node2BBCode($doc, 'img', array('alt'=>'/(.+)/'), '$1', '');
//self::node2BBCode($doc, 'img', array('title'=>'/(.+)/'), '$1', '');
//self::node2BBCode($doc, 'img', array(), '', '');
if (!$compact) {
self::tagToBBCode($doc, 'img', ['src' => '/(.+)/'], ' [img]$1', '[/img] ');
} else {
@ -688,4 +718,263 @@ class HTML
return $markdown;
}
/**
* @brief Convert video HTML to BBCode tags
*
* @param string $s
* @return string
*/
public static function toBBCodeVideo($s)
{
$s = preg_replace(
'#<object[^>]+>(.*?)https?://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+)(.*?)</object>#ism',
'[youtube]$2[/youtube]',
$s
);
$s = preg_replace(
'#<iframe[^>](.*?)https?://www.youtube.com/embed/([A-Za-z0-9\-_=]+)(.*?)</iframe>#ism',
'[youtube]$2[/youtube]',
$s
);
$s = preg_replace(
'#<iframe[^>](.*?)https?://player.vimeo.com/video/([0-9]+)(.*?)</iframe>#ism',
'[vimeo]$2[/vimeo]',
$s
);
return $s;
}
/**
* transform link href and img src from relative to absolute
*
* @param string $text
* @param string $base base url
* @return string
*/
public static function relToAbs($text, $base)
{
if (empty($base)) {
return $text;
}
$base = rtrim($base, '/');
$base2 = $base . "/";
// Replace links
$pattern = "/<a([^>]*) href=\"(?!http|https|\/)([^\"]*)\"/";
$replace = "<a\${1} href=\"" . $base2 . "\${2}\"";
$text = preg_replace($pattern, $replace, $text);
$pattern = "/<a([^>]*) href=\"(?!http|https)([^\"]*)\"/";
$replace = "<a\${1} href=\"" . $base . "\${2}\"";
$text = preg_replace($pattern, $replace, $text);
// Replace images
$pattern = "/<img([^>]*) src=\"(?!http|https|\/)([^\"]*)\"/";
$replace = "<img\${1} src=\"" . $base2 . "\${2}\"";
$text = preg_replace($pattern, $replace, $text);
$pattern = "/<img([^>]*) src=\"(?!http|https)([^\"]*)\"/";
$replace = "<img\${1} src=\"" . $base . "\${2}\"";
$text = preg_replace($pattern, $replace, $text);
// Done
return $text;
}
/**
* return div element with class 'clear'
* @return string
* @deprecated
*/
public static function clearDiv()
{
return '<div class="clear"></div>';
}
/**
* Loader for infinite scrolling
*
* @return string html for loader
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function scrollLoader()
{
$tpl = Renderer::getMarkupTemplate("scroll_loader.tpl");
return Renderer::replaceMacros($tpl, [
'wait' => L10n::t('Loading more entries...'),
'end' => L10n::t('The end')
]);
}
/**
* Get html for contact block.
*
* @deprecated since version 2019.03
* @see ContactBlock::getHTML()
* @return string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function contactBlock()
{
$a = \get_app();
return ContactBlock::getHTML($a->profile);
}
/**
* @brief Format contacts as picture links or as text links
*
* @param array $contact Array with contacts which contains an array with
* int 'id' => The ID of the contact
* int 'uid' => The user ID of the user who owns this data
* string 'name' => The name of the contact
* string 'url' => The url to the profile page of the contact
* string 'addr' => The webbie of the contact (e.g.) username@friendica.com
* string 'network' => The network to which the contact belongs to
* string 'thumb' => The contact picture
* string 'click' => js code which is performed when clicking on the contact
* @param boolean $redirect If true try to use the redir url if it's possible
* @param string $class CSS class for the
* @param boolean $textmode If true display the contacts as text links
* if false display the contacts as picture links
* @return string Formatted html
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function micropro($contact, $redirect = false, $class = '', $textmode = false)
{
// Use the contact URL if no address is available
if (empty($contact['addr'])) {
$contact["addr"] = $contact["url"];
}
$url = $contact['url'];
$sparkle = '';
$redir = false;
if ($redirect) {
$url = Contact::magicLink($contact['url']);
if (strpos($url, 'redir/') === 0) {
$sparkle = ' sparkle';
}
}
// If there is some js available we don't need the url
if (!empty($contact['click'])) {
$url = '';
}
return Renderer::replaceMacros(Renderer::getMarkupTemplate(($textmode)?'micropro_txt.tpl':'micropro_img.tpl'), [
'$click' => defaults($contact, 'click', ''),
'$class' => $class,
'$url' => $url,
'$photo' => ProxyUtils::proxifyUrl($contact['thumb'], false, ProxyUtils::SIZE_THUMB),
'$name' => $contact['name'],
'title' => $contact['name'] . ' [' . $contact['addr'] . ']',
'$parkle' => $sparkle,
'$redir' => $redir
]);
}
/**
* Search box.
*
* @param string $s Search query.
* @param string $id HTML id
* @param string $url Search url.
* @param bool $aside Display the search widgit aside.
*
* @return string Formatted HTML.
* @throws \Exception
*/
public static function search($s, $id = 'search-box', $aside = true)
{
$mode = 'text';
if (strpos($s, '#') === 0) {
$mode = 'tag';
}
$save_label = $mode === 'text' ? L10n::t('Save') : L10n::t('Follow');
$values = [
'$s' => $s,
'$q' => urlencode($s),
'$id' => $id,
'$search_label' => L10n::t('Search'),
'$save_label' => $save_label,
'$search_hint' => L10n::t('@name, !forum, #tags, content'),
'$mode' => $mode,
'$return_url' => urlencode('search?q=' . urlencode($s)),
];
if (!$aside) {
$values['$search_options'] = [
'fulltext' => L10n::t('Full Text'),
'tags' => L10n::t('Tags'),
'contacts' => L10n::t('Contacts')
];
if (Config::get('system', 'poco_local_search')) {
$values['$searchoption']['forums'] = L10n::t('Forums');
}
}
return Renderer::replaceMacros(Renderer::getMarkupTemplate('searchbox.tpl'), $values);
}
/**
* Replace naked text hyperlink with HTML formatted hyperlink
*
* @param string $s
* @return string
*/
public static function toLink($s)
{
$s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\'\%\$\!\+]*)/", ' <a href="$1" target="_blank">$1</a>', $s);
$s = preg_replace("/\<(.*?)(src|href)=(.*?)\&amp\;(.*?)\>/ism", '<$1$2=$3&$4>', $s);
return $s;
}
/**
* Given a HTML text and a set of filtering reasons, adds a content hiding header with the provided reasons
*
* Reasons are expected to have been translated already.
*
* @param string $html
* @param array $reasons
* @return string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function applyContentFilter($html, array $reasons)
{
if (count($reasons)) {
$tpl = Renderer::getMarkupTemplate('wall/content_filter.tpl');
$html = Renderer::replaceMacros($tpl, [
'$reasons' => $reasons,
'$rnd' => Strings::getRandomHex(8),
'$openclose' => L10n::t('Click to open/close'),
'$html' => $html
]);
}
return $html;
}
/**
* replace html amp entity with amp char
* @param string $s
* @return string
*/
public static function unamp($s)
{
return str_replace('&amp;', '&', $s);
}
}

View file

@ -7,9 +7,8 @@
namespace Friendica\Content\Text;
use Friendica\BaseObject;
use Friendica\Core\System;
use Friendica\Model\Contact;
use Michelf\MarkdownExtra;
use Friendica\Content\Text\HTML;
/**
* Friendica-specific usage of Markdown
@ -26,16 +25,25 @@ class Markdown extends BaseObject
* @param string $text
* @param bool $hardwrap
* @return string
* @throws \Exception
*/
public static function convert($text, $hardwrap = true) {
$stamp1 = microtime(true);
$MarkdownParser = new MarkdownExtra();
$MarkdownParser->hard_wrap = $hardwrap;
$MarkdownParser->code_class_prefix = 'language-';
$MarkdownParser = new MarkdownParser();
$MarkdownParser->code_class_prefix = 'language-';
$MarkdownParser->hard_wrap = $hardwrap;
$MarkdownParser->hashtag_protection = true;
$MarkdownParser->url_filter_func = function ($url) {
if (strpos($url, '#') === 0) {
$url = ltrim($_SERVER['REQUEST_URI'], '/') . $url;
}
return $url;
};
$html = $MarkdownParser->transform($text);
self::getApp()->saveTimestamp($stamp1, "parser");
self::getApp()->getProfiler()->saveTimestamp($stamp1, "parser", System::callstack());
return $html;
}
@ -44,27 +52,32 @@ class Markdown extends BaseObject
* @brief Callback function to replace a Diaspora style mention in a mention for Friendica
*
* @param array $match Matching values for the callback
* [1] = mention type (@ or !)
* [2] = name (optional)
* [3] = address
* @return string Replaced mention
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
private static function diasporaMention2BBCodeCallback($match)
{
if ($match[2] == '') {
if ($match[3] == '') {
return;
}
$data = Contact::getDetailsByAddr($match[2]);
$data = Contact::getDetailsByAddr($match[3]);
if (empty($data)) {
return;
}
$name = $match[1];
$name = $match[2];
if ($name == '') {
$name = $data['name'];
}
return '@[url=' . $data['url'] . ']' . $name . '[/url]';
return $match[1] . '[url=' . $data['url'] . ']' . $name . '[/url]';
}
/*
@ -77,27 +90,17 @@ class Markdown extends BaseObject
{
$s = html_entity_decode($s, ENT_COMPAT, 'UTF-8');
// Handles single newlines
$s = str_replace("\r\n", "\n", $s);
$s = str_replace("\n", " \n", $s);
$s = str_replace("\r", " \n", $s);
// Replace lonely stars in lines not starting with it with literal stars
$s = preg_replace('/^([^\*]+)\*([^\*]*)$/im', '$1\*$2', $s);
// The parser cannot handle paragraphs correctly
$s = str_replace(['</p>', '<p>', '<p dir="ltr">'], ['<br>', '<br>', '<br>'], $s);
// Escaping the hash tags
$s = preg_replace('/\#([^\s\#])/', '&#35;$1', $s);
// Escaping hashtags that could be titles
$s = preg_replace('/^\#([^\s\#])/im', '\#$1', $s);
$s = self::convert($s);
$regexp = "/@\{(?:([^\}]+?); )?([^\} ]+)\}/";
$regexp = "/([@!])\{(?:([^\}]+?); ?)?([^\} ]+)\}/";
$s = preg_replace_callback($regexp, ['self', 'diasporaMention2BBCodeCallback'], $s);
$s = str_replace('&#35;', '#', $s);
$s = HTML::toBBCode($s);
// protect the recycle symbol from turning into a tag, but without unescaping angles and naked ampersands

View file

@ -0,0 +1,18 @@
<?php
namespace Friendica\Content\Text;
use Friendica\Util\Strings;
use Michelf\MarkdownExtra;
class MarkdownParser extends MarkdownExtra
{
protected function doAutoLinks($text)
{
$text = parent::doAutoLinks($text);
$text = preg_replace_callback(Strings::autoLinkRegEx(),
array($this, '_doAutoLinks_url_callback'), $text);
return $text;
}
}

View file

@ -9,9 +9,9 @@ class Plaintext
/**
* Shortens message
*
* @param type $msg
* @param type $limit
* @return type
* @param string $msg
* @param int $limit
* @return string
*
* @todo For Twitter URLs aren't shortened, but they have to be calculated as if.
*/

View file

@ -4,21 +4,25 @@
*/
namespace Friendica\Content;
use Friendica\Content\ContactSelector;
use Friendica\Content\Feature;
use Friendica\Core\Addon;
use Friendica\Core\Config;
use Friendica\Core\L10n;
use Friendica\Core\PConfig;
use Friendica\Core\Protocol;
use Friendica\Core\Renderer;
use Friendica\Core\System;
use Friendica\Core\Session;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Model\FileTag;
use Friendica\Model\GContact;
use Friendica\Model\Item;
use Friendica\Model\Profile;
require_once 'boot.php';
require_once 'include/dba.php';
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Proxy as ProxyUtils;
use Friendica\Util\Strings;
use Friendica\Util\Temporal;
use Friendica\Util\XML;
class Widget
{
@ -26,10 +30,12 @@ class Widget
* Return the follow widget
*
* @param string $value optional, default empty
* @return string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function follow($value = "")
{
return replace_macros(get_markup_template('follow.tpl'), array(
return Renderer::replaceMacros(Renderer::getMarkupTemplate('widget/follow.tpl'), array(
'$connect' => L10n::t('Add New Contact'),
'$desc' => L10n::t('Enter address or web location'),
'$hint' => L10n::t('Example: bob@example.com, http://example.com/barbara'),
@ -43,11 +49,11 @@ class Widget
*/
public static function findPeople()
{
$a = get_app();
$a = \get_app();
$global_dir = Config::get('system', 'directory');
if (Config::get('system', 'invitation_only')) {
$x = PConfig::get(local_user(), 'system', 'invites_remaining');
$x = intval(PConfig::get(local_user(), 'system', 'invites_remaining'));
if ($x || is_site_admin()) {
$a->page['aside'] .= '<div class="side-link widget" id="side-invite-remain">'
. L10n::tt('%d invitation available', '%d invitations available', $x)
@ -72,7 +78,7 @@ class Widget
$aside = [];
$aside['$nv'] = $nv;
return replace_macros(get_markup_template('peoplefind.tpl'), $aside);
return Renderer::replaceMacros(Renderer::getMarkupTemplate('widget/peoplefind.tpl'), $aside);
}
/**
@ -118,11 +124,95 @@ class Widget
return $network_filter;
}
/**
* Display a generic filter widget based on a list of options
*
* The options array must be the following format:
* [
* [
* 'ref' => {filter value},
* 'name' => {option name}
* ],
* ...
* ]
*
* @param string $type The filter query string key
* @param string $title
* @param string $desc
* @param string $all The no filter label
* @param string $baseUrl The full page request URI
* @param array $options
* @param string $selected The currently selected filter option value
* @return string
* @throws \Exception
*/
private static function filter($type, $title, $desc, $all, $baseUrl, array $options, $selected = null)
{
$queryString = parse_url($baseUrl, PHP_URL_QUERY);
$queryArray = [];
if ($queryString) {
parse_str($queryString, $queryArray);
unset($queryArray[$type]);
if (count($queryArray)) {
$baseUrl = substr($baseUrl, 0, strpos($baseUrl, '?')) . '?' . http_build_query($queryArray) . '&';
} else {
$baseUrl = substr($baseUrl, 0, strpos($baseUrl, '?')) . '?';
}
} else {
$baseUrl = trim($baseUrl, '?') . '?';
}
return Renderer::replaceMacros(Renderer::getMarkupTemplate('widget/filter.tpl'), [
'$type' => $type,
'$title' => $title,
'$desc' => $desc,
'$selected' => $selected,
'$all_label' => $all,
'$options' => $options,
'$base' => $baseUrl,
]);
}
/**
* Return networks widget
*
* @param string $baseurl baseurl
* @param string $selected optional, default empty
* @return string
* @throws \Exception
*/
public static function contactRels($baseurl, $selected = '')
{
if (!local_user()) {
return '';
}
$options = [
['ref' => 'followers', 'name' => L10n::t('Followers')],
['ref' => 'following', 'name' => L10n::t('Following')],
['ref' => 'mutuals', 'name' => L10n::t('Mutual friends')],
];
return self::filter(
'rel',
L10n::t('Relationships'),
'',
L10n::t('All Contacts'),
$baseurl,
$options,
$selected
);
}
/**
* Return networks widget
*
* @param string $baseurl baseurl
* @param string $selected optional, default empty
* @return string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function networks($baseurl, $selected = '')
{
@ -136,13 +226,13 @@ class Widget
$extra_sql = self::unavailableNetworks();
$r = DBA::p("SELECT DISTINCT(`network`) FROM `contact` WHERE `uid` = ? AND `network` != '' $extra_sql ORDER BY `network`",
$r = DBA::p("SELECT DISTINCT(`network`) FROM `contact` WHERE `uid` = ? AND NOT `deleted` AND `network` != '' $extra_sql ORDER BY `network`",
local_user()
);
$nets = array();
while ($rr = DBA::fetch($r)) {
$nets[] = array('ref' => $rr['network'], 'name' => ContactSelector::networkToName($rr['network']), 'selected' => (($selected == $rr['network']) ? 'selected' : '' ));
$nets[] = ['ref' => $rr['network'], 'name' => ContactSelector::networkToName($rr['network'])];
}
DBA::close($r);
@ -150,14 +240,15 @@ class Widget
return '';
}
return replace_macros(get_markup_template('nets.tpl'), array(
'$title' => L10n::t('Networks'),
'$desc' => '',
'$sel_all' => (($selected == '') ? 'selected' : ''),
'$all' => L10n::t('All Networks'),
'$nets' => $nets,
'$base' => $baseurl,
));
return self::filter(
'nets',
L10n::t('Protocols'),
'',
L10n::t('All Protocols'),
$baseurl,
$nets,
$selected
);
}
/**
@ -165,6 +256,8 @@ class Widget
*
* @param string $baseurl baseurl
* @param string $selected optional, default empty
* @return string|void
* @throws \Exception
*/
public static function fileAs($baseurl, $selected = '')
{
@ -172,33 +265,25 @@ class Widget
return '';
}
if (!Feature::isEnabled(local_user(), 'filing')) {
return '';
}
$saved = PConfig::get(local_user(), 'system', 'filetags');
if (!strlen($saved)) {
return;
}
$matches = false;
$terms = array();
$cnt = preg_match_all('/\[(.*?)\]/', $saved, $matches, PREG_SET_ORDER);
if ($cnt) {
foreach ($matches as $mtch) {
$unescaped = xmlify(file_tag_decode($mtch[1]));
$terms[] = array('name' => $unescaped, 'selected' => (($selected == $unescaped) ? 'selected' : ''));
}
$terms = [];
foreach (FileTag::fileToArray($saved) as $savedFolderName) {
$terms[] = ['ref' => $savedFolderName, 'name' => $savedFolderName];
}
return replace_macros(get_markup_template('fileas_widget.tpl'), array(
'$title' => L10n::t('Saved Folders'),
'$desc' => '',
'$sel_all' => (($selected == '') ? 'selected' : ''),
'$all' => L10n::t('Everything'),
'$terms' => $terms,
'$base' => $baseurl,
));
return self::filter(
'file',
L10n::t('Saved Folders'),
'',
L10n::t('Everything'),
$baseurl,
$terms,
$selected
);
}
/**
@ -206,45 +291,46 @@ class Widget
*
* @param string $baseurl baseurl
* @param string $selected optional, default empty
* @return string|void
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function categories($baseurl, $selected = '')
{
$a = get_app();
$a = \get_app();
if (!Feature::isEnabled($a->profile['profile_uid'], 'categories')) {
$uid = intval($a->profile['profile_uid']);
if (!Feature::isEnabled($uid, 'categories')) {
return '';
}
$saved = PConfig::get($a->profile['profile_uid'], 'system', 'filetags');
$saved = PConfig::get($uid, 'system', 'filetags');
if (!strlen($saved)) {
return;
}
$matches = false;
$terms = array();
$cnt = preg_match_all('/<(.*?)>/', $saved, $matches, PREG_SET_ORDER);
if ($cnt) {
foreach ($matches as $mtch) {
$unescaped = xmlify(file_tag_decode($mtch[1]));
$terms[] = array('name' => $unescaped, 'selected' => (($selected == $unescaped) ? 'selected' : ''));
}
foreach (FileTag::fileToArray($saved, 'category') as $savedFolderName) {
$terms[] = ['ref' => $savedFolderName, 'name' => $savedFolderName];
}
return replace_macros(get_markup_template('categories_widget.tpl'), array(
'$title' => L10n::t('Categories'),
'$desc' => '',
'$sel_all' => (($selected == '') ? 'selected' : ''),
'$all' => L10n::t('Everything'),
'$terms' => $terms,
'$base' => $baseurl,
));
return self::filter(
'category',
L10n::t('Categories'),
'',
L10n::t('Everything'),
$baseurl,
$terms,
$selected
);
}
/**
* Return common friends visitor widget
*
* @param string $profile_uid uid
* @return string|void
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function commonFriendsVisitor($profile_uid)
{
@ -252,25 +338,18 @@ class Widget
return;
}
$cid = $zcid = 0;
$zcid = 0;
if (!empty($_SESSION['remote'])) {
foreach ($_SESSION['remote'] as $visitor) {
if ($visitor['uid'] == $profile_uid) {
$cid = $visitor['cid'];
break;
}
}
}
$cid = Session::getRemoteContactID($profile_uid);
if (!$cid) {
if (Profile::getMyURL()) {
$contact = DBA::selectFirst('contact', ['id'],
['nurl' => normalise_link(Profile::getMyURL()), 'uid' => $profile_uid]);
['nurl' => Strings::normaliseLink(Profile::getMyURL()), 'uid' => $profile_uid]);
if (DBA::isResult($contact)) {
$cid = $contact['id'];
} else {
$gcontact = DBA::selectFirst('gcontact', ['id'], ['nurl' => normalise_link(Profile::getMyURL())]);
$gcontact = DBA::selectFirst('gcontact', ['id'], ['nurl' => Strings::normaliseLink(Profile::getMyURL())]);
if (DBA::isResult($gcontact)) {
$zcid = $gcontact['id'];
}
@ -298,41 +377,130 @@ class Widget
$r = GContact::commonFriendsZcid($profile_uid, $zcid, 0, 5, true);
}
return replace_macros(get_markup_template('remote_friends_common.tpl'), array(
'$desc' => L10n::tt("%d contact in common", "%d contacts in common", $t),
'$base' => System::baseUrl(),
'$uid' => $profile_uid,
'$cid' => (($cid) ? $cid : '0'),
if (!DBA::isResult($r)) {
return;
}
$entries = [];
foreach ($r as $rr) {
$entry = [
'url' => Contact::magicLink($rr['url']),
'name' => $rr['name'],
'photo' => ProxyUtils::proxifyUrl($rr['photo'], false, ProxyUtils::SIZE_THUMB),
];
$entries[] = $entry;
}
$tpl = Renderer::getMarkupTemplate('widget/remote_friends_common.tpl');
return Renderer::replaceMacros($tpl, [
'$desc' => L10n::tt("%d contact in common", "%d contacts in common", $t),
'$base' => System::baseUrl(),
'$uid' => $profile_uid,
'$cid' => (($cid) ? $cid : '0'),
'$linkmore' => (($t > 5) ? 'true' : ''),
'$more' => L10n::t('show more'),
'$items' => $r)
);
'$more' => L10n::t('show more'),
'$items' => $entries
]);
}
/**
* Insert a tag cloud widget for the present profile.
*
* @brief Insert a tag cloud widget for the present profile.
* @param int $limit Max number of displayed tags.
* @param int $limit Max number of displayed tags.
* @return string HTML formatted output.
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function tagCloud($limit = 50)
{
$a = get_app();
$a = \get_app();
if (!$a->profile['profile_uid'] || !$a->profile['url']) {
$uid = intval($a->profile['profile_uid']);
if (!$uid || !$a->profile['url']) {
return '';
}
if (Feature::isEnabled($a->profile['profile_uid'], 'tagadelic')) {
if (Feature::isEnabled($uid, 'tagadelic')) {
$owner_id = Contact::getIdForURL($a->profile['url'], 0, true);
if (!$owner_id) {
return '';
}
return Widget\TagCloud::getHTML($a->profile['profile_uid'], $limit, $owner_id, 'wall');
return Widget\TagCloud::getHTML($uid, $limit, $owner_id, 'wall');
}
return '';
}
/**
* @param string $url Base page URL
* @param int $uid User ID consulting/publishing posts
* @param bool $wall True: Posted by User; False: Posted to User (network timeline)
* @return string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function postedByYear(string $url, int $uid, bool $wall)
{
$o = '';
if (!Feature::isEnabled($uid, 'archives')) {
return $o;
}
$visible_years = PConfig::get($uid, 'system', 'archive_visible_years', 5);
/* arrange the list in years */
$dnow = DateTimeFormat::localNow('Y-m-d');
$ret = [];
$dthen = Item::firstPostDate($uid, $wall);
if ($dthen) {
// Set the start and end date to the beginning of the month
$dnow = substr($dnow, 0, 8) . '01';
$dthen = substr($dthen, 0, 8) . '01';
/*
* Starting with the current month, get the first and last days of every
* month down to and including the month of the first post
*/
while (substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
$dyear = intval(substr($dnow, 0, 4));
$dstart = substr($dnow, 0, 8) . '01';
$dend = substr($dnow, 0, 8) . Temporal::getDaysInMonth(intval($dnow), intval(substr($dnow, 5)));
$start_month = DateTimeFormat::utc($dstart, 'Y-m-d');
$end_month = DateTimeFormat::utc($dend, 'Y-m-d');
$str = L10n::getDay(DateTimeFormat::utc($dnow, 'F'));
if (empty($ret[$dyear])) {
$ret[$dyear] = [];
}
$ret[$dyear][] = [$str, $end_month, $start_month];
$dnow = DateTimeFormat::utc($dnow . ' -1 month', 'Y-m-d');
}
}
if (!DBA::isResult($ret)) {
return $o;
}
$cutoff_year = intval(DateTimeFormat::localNow('Y')) - $visible_years;
$cutoff = array_key_exists($cutoff_year, $ret);
$o = Renderer::replaceMacros(Renderer::getMarkupTemplate('widget/posted_date.tpl'),[
'$title' => L10n::t('Archives'),
'$size' => $visible_years,
'$cutoff_year' => $cutoff_year,
'$cutoff' => $cutoff,
'$url' => $url,
'$dates' => $ret,
'$showmore' => L10n::t('show more')
]);
return $o;
}
}

View file

@ -8,9 +8,7 @@ namespace Friendica\Content\Widget;
use Friendica\Content\Feature;
use Friendica\Core\L10n;
require_once 'boot.php';
require_once 'include/text.php';
use Friendica\Core\Renderer;
/**
* TagCloud widget
@ -23,15 +21,16 @@ class CalendarExport
* @brief Get the events widget.
*
* @return string Formated HTML of the calendar widget.
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function getHTML() {
$a = get_app();
$a = \get_app();
if (empty($a->data['user'])) {
return;
}
$owner_uid = $a->data['user']['uid'];
$owner_uid = intval($a->data['user']['uid']);
// The permission testing is a little bit tricky because we have to respect many cases.
@ -60,8 +59,8 @@ class CalendarExport
// of the profile page it should be the personal /events page. So we can use $a->user.
$user = defaults($a->data['user'], 'nickname', $a->user['nickname']);
$tpl = get_markup_template("events_aside.tpl");
$return = replace_macros($tpl, [
$tpl = Renderer::getMarkupTemplate("widget/events.tpl");
$return = Renderer::replaceMacros($tpl, [
'$etitle' => L10n::t("Export"),
'$export_ical' => L10n::t("Export calendar as ical"),
'$export_csv' => L10n::t("Export calendar as csv"),

View file

@ -0,0 +1,119 @@
<?php
/*
* @file src/Content/Widget/ContactBlock.php
*/
namespace Friendica\Content\Widget;
use Friendica\Content\Text\HTML;
use Friendica\Core\Hook;
use Friendica\Core\L10n;
use Friendica\Core\PConfig;
use Friendica\Core\Protocol;
use Friendica\Core\Renderer;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Model\User;
/**
* ContactBlock widget
*
* @author Hypolite Petovan
*/
class ContactBlock
{
/**
* Get HTML for contact block
*
* @template widget/contacts.tpl
* @hook contact_block_end (contacts=>array, output=>string)
* @return string
*/
public static function getHTML(array $profile)
{
$o = '';
$shown = PConfig::get($profile['uid'], 'system', 'display_friend_count', 24);
if ($shown == 0) {
return $o;
}
if (!empty($profile['hide-friends'])) {
return $o;
}
$contacts = [];
$total = DBA::count('contact', [
'uid' => $profile['uid'],
'self' => false,
'blocked' => false,
'pending' => false,
'hidden' => false,
'archive' => false,
'network' => [Protocol::DFRN, Protocol::ACTIVITYPUB, Protocol::OSTATUS, Protocol::DIASPORA, Protocol::FEED],
]);
$contacts_title = L10n::t('No contacts');
$micropro = [];
if ($total) {
// Only show followed for personal accounts, followers for pages
if (defaults($profile, 'account-type', User::ACCOUNT_TYPE_PERSON) == User::ACCOUNT_TYPE_PERSON) {
$rel = [Contact::SHARING, Contact::FRIEND];
} else {
$rel = [Contact::FOLLOWER, Contact::FRIEND];
}
$contact_ids_stmt = DBA::select('contact', ['id'], [
'uid' => $profile['uid'],
'self' => false,
'blocked' => false,
'pending' => false,
'hidden' => false,
'archive' => false,
'rel' => $rel,
'network' => Protocol::FEDERATED,
], ['limit' => $shown]);
if (DBA::isResult($contact_ids_stmt)) {
$contact_ids = [];
while($contact = DBA::fetch($contact_ids_stmt)) {
$contact_ids[] = $contact["id"];
}
$contacts_stmt = DBA::select('contact', ['id', 'uid', 'addr', 'url', 'name', 'thumb', 'network'], ['id' => $contact_ids]);
if (DBA::isResult($contacts_stmt)) {
$contacts_title = L10n::tt('%d Contact', '%d Contacts', $total);
$micropro = [];
while ($contact = DBA::fetch($contacts_stmt)) {
$contacts[] = $contact;
$micropro[] = HTML::micropro($contact, true, 'mpfriend');
}
}
DBA::close($contacts_stmt);
}
DBA::close($contact_ids_stmt);
}
$tpl = Renderer::getMarkupTemplate('widget/contacts.tpl');
$o = Renderer::replaceMacros($tpl, [
'$contacts' => $contacts_title,
'$nickname' => $profile['nickname'],
'$viewcontacts' => L10n::t('View Contacts'),
'$micropro' => $micropro,
]);
$arr = ['contacts' => $contacts, 'output' => $o];
Hook::callAll('contact_block_end', $arr);
return $o;
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace Friendica\Content\Widget;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
use Friendica\Database\DBA;
class SavedSearches
{
/**
* @param string $return_url
* @param string $search
* @return string
* @throws \Exception
*/
public static function getHTML($return_url, $search = '')
{
$o = '';
$saved_searches = DBA::select('search', ['id', 'term'], ['uid' => local_user()]);
if (DBA::isResult($saved_searches)) {
$saved = [];
foreach ($saved_searches as $saved_search) {
$saved[] = [
'id' => $saved_search['id'],
'term' => $saved_search['term'],
'encodedterm' => urlencode($saved_search['term']),
'delete' => L10n::t('Remove term'),
'selected' => $search == $saved_search['term'],
];
}
$tpl = Renderer::getMarkupTemplate('widget/saved_searches.tpl');
$o = Renderer::replaceMacros($tpl, [
'$title' => L10n::t('Saved Searches'),
'$add' => '',
'$searchbox' => '',
'$saved' => $saved,
'$return_url' => urlencode($return_url),
]);
}
return $o;
}
}

View file

@ -7,12 +7,10 @@
namespace Friendica\Content\Widget;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Model\Item;
use Friendica\Util\Security;
require_once 'include/dba.php';
/**
* TagCloud widget
@ -25,13 +23,14 @@ class TagCloud
* Construct a tag/term cloud block for an user.
*
* @brief Construct a tag/term cloud block for an user.
* @param int $uid The user ID.
* @param int $count Max number of displayed tags/terms.
* @param int $owner_id The contact ID of the owner of the tagged items.
* @param string $flags Special item flags.
* @param int $type The tag/term type.
* @param int $uid The user ID.
* @param int $count Max number of displayed tags/terms.
* @param int $owner_id The contact ID of the owner of the tagged items.
* @param string $flags Special item flags.
* @param int $type The tag/term type.
*
* @return string HTML formatted output.
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function getHTML($uid, $count = 0, $owner_id = 0, $flags = '', $type = TERM_HASHTAG)
{
@ -41,6 +40,7 @@ class TagCloud
$contact = DBA::selectFirst('contact', ['url'], ['uid' => $uid, 'self' => true]);
$url = System::removedBaseUrl($contact['url']);
$tags = [];
foreach ($r as $rr) {
$tag['level'] = $rr[2];
$tag['url'] = $url . '?tag=' . urlencode($rr[0]);
@ -49,8 +49,8 @@ class TagCloud
$tags[] = $tag;
}
$tpl = get_markup_template('tagblock_widget.tpl');
$o = replace_macros($tpl, [
$tpl = Renderer::getMarkupTemplate('widget/tagcloud.tpl');
$o = Renderer::replaceMacros($tpl, [
'$title' => L10n::t('Tags'),
'$tags' => $tags
]);
@ -64,13 +64,14 @@ class TagCloud
*
* @brief Get alphabetical sorted array of used tags/terms of an user including
* a weighting by frequency of use.
* @param int $uid The user ID.
* @param int $count Max number of displayed tags/terms.
* @param int $owner_id The contact id of the owner of the tagged items.
* @param string $flags Special item flags.
* @param int $type The tag/term type.
* @param int $uid The user ID.
* @param int $count Max number of displayed tags/terms.
* @param int $owner_id The contact id of the owner of the tagged items.
* @param string $flags Special item flags.
* @param int $type The tag/term type.
*
* @return arr Alphabetical sorted array of used tags of an user.
* @return array Alphabetical sorted array of used tags of an user.
* @throws \Exception
*/
private static function tagadelic($uid, $count = 0, $owner_id = 0, $flags = '', $type = TERM_HASHTAG)
{
@ -88,7 +89,7 @@ class TagCloud
}
// Fetch tags
$r = DBA::p("SELECT `term`, COUNT(`term`) AS `total` FROM `term`
$tag_stmt = DBA::p("SELECT `term`, COUNT(`term`) AS `total` FROM `term`
LEFT JOIN `item` ON `term`.`oid` = `item`.`id`
WHERE `term`.`uid` = ? AND `term`.`type` = ?
AND `term`.`otype` = ?
@ -99,10 +100,12 @@ class TagCloud
$type,
TERM_OBJ_POST
);
if (!DBA::isResult($r)) {
if (!DBA::isResult($tag_stmt)) {
return [];
}
$r = DBA::toArray($tag_stmt);
return self::tagCalc($r);
}
@ -113,7 +116,7 @@ class TagCloud
* @param array $arr Array of tags/terms with tag/term name and total count of use.
* @return array Alphabetical sorted array of used tags/terms of an user.
*/
private static function tagCalc($arr)
private static function tagCalc(array $arr)
{
$tags = [];
$min = 1e9;
@ -147,8 +150,8 @@ class TagCloud
* Compare function to sort tags/terms alphabetically.
*
* @brief Compare function to sort tags/terms alphabetically.
* @param type $a
* @param type $b
* @param string $a
* @param string $b
*
* @return int
*/

View file

@ -0,0 +1,41 @@
<?php
namespace Friendica\Content\Widget;
use Friendica\Core\Cache;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
use Friendica\Database\DBA;
use Friendica\Model\Term;
/**
* Trending tags aside widget for the community pages, handles both local and global scopes
*
* @package Friendica\Content\Widget
*/
class TrendingTags
{
/**
* @param string $content 'global' (all posts) or 'local' (this node's posts only)
* @param int $period Period in hours to consider posts
* @return string
* @throws \Exception
*/
public static function getHTML($content = 'global', int $period = 24)
{
if ($content == 'local') {
$tags = Term::getLocalTrendingHashtags($period, 20);
} else {
$tags = Term::getGlobalTrendingHashtags($period, 20);
}
$tpl = Renderer::getMarkupTemplate('widget/trending_tags.tpl');
$o = Renderer::replaceMacros($tpl, [
'$title' => L10n::tt('Trending Tags (last %d hour)', 'Trending Tags (last %d hours)', $period),
'$more' => L10n::t('More Trending Tags'),
'$tags' => $tags,
]);
return $o;
}
}

View file

@ -8,10 +8,10 @@ namespace Friendica\Core;
use Friendica\BaseObject;
use Friendica\Content\Feature;
use Friendica\Core\Protocol;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Model\GContact;
use Friendica\Core\Session;
use Friendica\Util\Network;
/**
@ -24,15 +24,16 @@ class ACL extends BaseObject
/**
* Returns a select input tag with all the contact of the local user
*
* @param string $selname Name attribute of the select input tag
* @param string $selclass Class attribute of the select input tag
* @param array $options Available options:
* - size: length of the select box
* - mutual_friends: Only used for the hook
* - single: Only used for the hook
* - exclude: Only used for the hook
* @param array $preselected Contact ID that should be already selected
* @param string $selname Name attribute of the select input tag
* @param string $selclass Class attribute of the select input tag
* @param array $options Available options:
* - size: length of the select box
* - mutual_friends: Only used for the hook
* - single: Only used for the hook
* - exclude: Only used for the hook
* @param array $preselected Contact ID that should be already selected
* @return string
* @throws \Exception
*/
public static function getSuggestContactSelectHTML($selname, $selclass, array $options = [], array $preselected = [])
{
@ -68,7 +69,7 @@ class ACL extends BaseObject
$x = ['options' => $options, 'size' => $size, 'single' => $single, 'mutual' => $mutual, 'exclude' => $exclude, 'networks' => $networks];
Addon::callHooks('contact_select_options', $x);
Hook::callAll('contact_select_options', $x);
$o = '';
@ -100,7 +101,7 @@ class ACL extends BaseObject
}
$stmt = DBA::p("SELECT `id`, `name`, `url`, `network` FROM `contact`
WHERE `uid` = ? AND NOT `self` AND NOT `blocked` AND NOT `pending` AND NOT `archive` AND `notify` != ''
WHERE `uid` = ? AND NOT `self` AND NOT `blocked` AND NOT `pending` AND NOT `archive` AND NOT `deleted` AND `notify` != ''
$sql_extra
ORDER BY `name` ASC ", intval(local_user())
);
@ -110,7 +111,7 @@ class ACL extends BaseObject
$arr = ['contact' => $contacts, 'entry' => $o];
// e.g. 'network_pre_contact_deny', 'profile_pre_contact_allow'
Addon::callHooks($a->module . '_pre_' . $selname, $arr);
Hook::callAll($a->module . '_pre_' . $selname, $arr);
if (DBA::isResult($contacts)) {
foreach ($contacts as $contact) {
@ -128,7 +129,7 @@ class ACL extends BaseObject
$o .= '</select>' . PHP_EOL;
Addon::callHooks($a->module . '_post_' . $selname, $o);
Hook::callAll($a->module . '_post_' . $selname, $o);
return $o;
}
@ -142,6 +143,7 @@ class ACL extends BaseObject
* @param int $size Length of the select box
* @param int $tabindex Select input tag tabindex attribute
* @return string
* @throws \Exception
*/
public static function getMessageContactSelectHTML($selname, $selclass, array $preselected = [], $size = 4, $tabindex = null)
{
@ -165,7 +167,7 @@ class ACL extends BaseObject
$o .= "<select name=\"$selname\" id=\"$selclass\" class=\"$selclass\" size=\"$size\"$tabindex_attr$hidepreselected>\r\n";
$stmt = DBA::p("SELECT `id`, `name`, `url`, `network` FROM `contact`
WHERE `uid` = ? AND NOT `self` AND NOT `blocked` AND NOT `pending` AND NOT `archive` AND `notify` != ''
WHERE `uid` = ? AND NOT `self` AND NOT `blocked` AND NOT `pending` AND NOT `archive` AND NOT `deleted` AND `notify` != ''
$sql_extra
ORDER BY `name` ASC ", intval(local_user())
);
@ -175,7 +177,7 @@ class ACL extends BaseObject
$arr = ['contact' => $contacts, 'entry' => $o];
// e.g. 'network_pre_contact_deny', 'profile_pre_contact_allow'
Addon::callHooks($a->module . '_pre_' . $selname, $arr);
Hook::callAll($a->module . '_pre_' . $selname, $arr);
$receiverlist = [];
@ -201,7 +203,7 @@ class ACL extends BaseObject
$o .= implode(', ', $receiverlist);
}
Addon::callHooks($a->module . '_post_' . $selname, $o);
Hook::callAll($a->module . '_post_' . $selname, $o);
return $o;
}
@ -216,6 +218,7 @@ class ACL extends BaseObject
*
* @param array $user
* @return array Hash of contact id lists
* @throws \Exception
*/
public static function getDefaultUserPermissions(array $user = null)
{
@ -252,25 +255,24 @@ class ACL extends BaseObject
* Return the full jot ACL selector HTML
*
* @param array $user User array
* @param array $default_permissions Static defaults permission array: ['allow_cid' => '', 'allow_gid' => '', 'deny_cid' => '', 'deny_gid' => '']
* @param bool $show_jotnets
* @param array $default_permissions Static defaults permission array: ['allow_cid' => '', 'allow_gid' => '', 'deny_cid' => '', 'deny_gid' => '']
* @return string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function getFullSelectorHTML(array $user, $show_jotnets = false, array $default_permissions = [])
public static function getFullSelectorHTML(array $user = null, $show_jotnets = false, array $default_permissions = [])
{
// Defaults user permissions
if (empty($default_permissions)) {
$default_permissions = self::getDefaultUserPermissions($user);
}
$jotnets = '';
$jotnets_fields = [];
if ($show_jotnets) {
$imap_disabled = !function_exists('imap_open') || Config::get('system', 'imap_disabled');
$mail_enabled = false;
$pubmail_enabled = false;
if (!$imap_disabled) {
if (function_exists('imap_open') && !Config::get('system', 'imap_disabled')) {
$mailacct = DBA::selectFirst('mailacct', ['pubmail'], ['`uid` = ? AND `server` != ""', local_user()]);
if (DBA::isResult($mailacct)) {
$mail_enabled = true;
@ -280,78 +282,43 @@ class ACL extends BaseObject
if (empty($default_permissions['hidewall'])) {
if ($mail_enabled) {
$selected = $pubmail_enabled ? ' checked="checked"' : '';
$jotnets .= '<div class="profile-jot-net"><input type="checkbox" name="pubmail_enable"' . $selected . ' value="1" /> ' . L10n::t("Post to Email") . '</div>';
$jotnets_fields[] = [
'type' => 'checkbox',
'field' => [
'pubmail_enable',
L10n::t('Post to Email'),
$pubmail_enabled
]
];
}
Addon::callHooks('jot_networks', $jotnets);
} else {
$jotnets .= L10n::t('Connectors disabled, since "%s" is enabled.',
L10n::t('Hide your profile details from unknown viewers?'));
Hook::callAll('jot_networks', $jotnets_fields);
}
}
$tpl = get_markup_template('acl_selector.tpl');
$o = replace_macros($tpl, [
$tpl = Renderer::getMarkupTemplate('acl_selector.tpl');
$o = Renderer::replaceMacros($tpl, [
'$showall' => L10n::t('Visible to everybody'),
'$show' => L10n::t('show'),
'$hide' => L10n::t('don\'t show'),
'$allowcid' => json_encode(defaults($default_permissions, 'allow_cid', '')),
'$allowgid' => json_encode(defaults($default_permissions, 'allow_gid', '')),
'$denycid' => json_encode(defaults($default_permissions, 'deny_cid', '')),
'$denygid' => json_encode(defaults($default_permissions, 'deny_gid', '')),
'$allowcid' => json_encode(defaults($default_permissions, 'allow_cid', [])), // we need arrays for Javascript since we call .remove() and .push() on this values
'$allowgid' => json_encode(defaults($default_permissions, 'allow_gid', [])),
'$denycid' => json_encode(defaults($default_permissions, 'deny_cid', [])),
'$denygid' => json_encode(defaults($default_permissions, 'deny_gid', [])),
'$networks' => $show_jotnets,
'$emailcc' => L10n::t('CC: email addresses'),
'$emtitle' => L10n::t('Example: bob@example.com, mary@example.com'),
'$jotnets' => $jotnets,
'$jotnets_enabled' => empty($default_permissions['hidewall']),
'$jotnets_summary' => L10n::t('Connectors'),
'$jotnets_fields' => $jotnets_fields,
'$jotnets_disabled_label' => L10n::t('Connectors disabled, since "%s" is enabled.', L10n::t('Hide your profile details from unknown viewers?')),
'$aclModalTitle' => L10n::t('Permissions'),
'$aclModalDismiss' => L10n::t('Close'),
'$features' => [
'aclautomention' => Feature::isEnabled($user['uid'], 'aclautomention') ? 'true' : 'false'
'aclautomention' => !empty($user['uid']) && Feature::isEnabled($user['uid'], 'aclautomention') ? 'true' : 'false'
],
]);
return $o;
}
/**
* Searching for global contacts for autocompletion
*
* @brief Searching for global contacts for autocompletion
* @param string $search Name or part of a name or nick
* @param string $mode Search mode (e.g. "community")
* @return array with the search results
*/
public static function contactAutocomplete($search, $mode)
{
if (Config::get('system', 'block_public') && !local_user() && !remote_user()) {
return [];
}
// don't search if search term has less than 2 characters
if (!$search || mb_strlen($search) < 2) {
return [];
}
if (substr($search, 0, 1) === '@') {
$search = substr($search, 1);
}
// check if searching in the local global contact table is enabled
if (Config::get('system', 'poco_local_search')) {
$return = GContact::searchByName($search, $mode);
} else {
$p = defaults($_GET, 'page', 1) != 1 ? '&p=' . defaults($_GET, 'page', 1) : '';
$curlResult = Network::curl(get_server() . '/lsearch?f=' . $p . '&search=' . urlencode($search));
if ($curlResult->isSuccess()) {
$lsearch = json_decode($curlResult->getBody(), true);
if (!empty($lsearch['results'])) {
$return = $lsearch['results'];
}
}
}
return defaults($return, []);
}
}

View file

@ -2,17 +2,24 @@
/**
* @file src/Core/Addon.php
*/
namespace Friendica\Core;
use Friendica\App;
use Friendica\BaseObject;
use Friendica\Database\DBA;
use Friendica\Util\Strings;
/**
* Some functions to handle addons
*/
class Addon extends BaseObject
{
/**
* The addon sub-directory
* @var string
*/
const DIRECTORY = 'addon';
/**
* List of the names of enabled addons
*
@ -20,6 +27,61 @@ class Addon extends BaseObject
*/
private static $addons = [];
/**
* Returns the list of available addons with their current status and info.
* This list is made from scanning the addon/ folder.
* Unsupported addons are excluded unless they already are enabled or system.show_unsupported_addon is set.
*
* @return array
* @throws \Exception
*/
public static function getAvailableList()
{
$addons = [];
$files = glob('addon/*/');
if (is_array($files)) {
foreach ($files as $file) {
if (is_dir($file)) {
list($tmp, $addon) = array_map('trim', explode('/', $file));
$info = self::getInfo($addon);
if (Config::get('system', 'show_unsupported_addons')
|| strtolower($info['status']) != 'unsupported'
|| self::isEnabled($addon)
) {
$addons[] = [$addon, (self::isEnabled($addon) ? 'on' : 'off'), $info];
}
}
}
}
return $addons;
}
/**
* Returns a list of addons that can be configured at the node level.
* The list is formatted for display in the admin panel aside.
*
* @return array
* @throws \Exception
*/
public static function getAdminList()
{
$addons_admin = [];
$addonsAdminStmt = DBA::select('addon', ['name'], ['plugin_admin' => 1], ['order' => ['name']]);
while ($addon = DBA::fetch($addonsAdminStmt)) {
$addons_admin[$addon['name']] = [
'url' => 'admin/addons/' . $addon['name'],
'name' => $addon['name'],
'class' => 'addon'
];
}
DBA::close($addonsAdminStmt);
return $addons_admin;
}
/**
* @brief Synchronize addons:
*
@ -71,11 +133,14 @@ class Addon extends BaseObject
* @brief uninstalls an addon.
*
* @param string $addon name of the addon
* @return boolean
* @return void
* @throws \Exception
*/
public static function uninstall($addon)
{
logger("Addons: uninstalling " . $addon);
$addon = Strings::sanitizeFilePathItem($addon);
Logger::notice("Addon {addon}: {action}", ['action' => 'uninstall', 'addon' => $addon]);
DBA::delete('addon', ['name' => $addon]);
@include_once('addon/' . $addon . '/' . $addon . '.php');
@ -84,7 +149,11 @@ class Addon extends BaseObject
$func();
}
DBA::delete('hook', ['file' => 'addon/' . $addon . '/' . $addon . '.php']);
unset(self::$addons[array_search($addon, self::$addons)]);
Addon::saveEnabledList();
}
/**
@ -92,20 +161,23 @@ class Addon extends BaseObject
*
* @param string $addon name of the addon
* @return bool
* @throws \Exception
*/
public static function install($addon)
{
// silently fail if addon was removed
$addon = Strings::sanitizeFilePathItem($addon);
// silently fail if addon was removed of if $addon is funky
if (!file_exists('addon/' . $addon . '/' . $addon . '.php')) {
return false;
}
logger("Addons: installing " . $addon);
Logger::notice("Addon {addon}: {action}", ['action' => 'install', 'addon' => $addon]);
$t = @filemtime('addon/' . $addon . '/' . $addon . '.php');
@include_once('addon/' . $addon . '/' . $addon . '.php');
if (function_exists($addon . '_install')) {
$func = $addon . '_install';
$func();
$func(self::getApp());
$addon_admin = (function_exists($addon . "_addon_admin") ? 1 : 0);
@ -123,9 +195,12 @@ class Addon extends BaseObject
if (!self::isEnabled($addon)) {
self::$addons[] = $addon;
}
Addon::saveEnabledList();
return true;
} else {
logger("Addons: FAILED installing " . $addon);
Logger::error("Addon {addon}: {action} failed", ['action' => 'install', 'addon' => $addon]);
return false;
}
}
@ -146,28 +221,26 @@ class Addon extends BaseObject
$addon_list = explode(',', $addons);
if (count($addon_list)) {
foreach ($addon_list as $addon) {
$addon = trim($addon);
$fname = 'addon/' . $addon . '/' . $addon . '.php';
foreach ($addon_list as $addon) {
$addon = Strings::sanitizeFilePathItem(trim($addon));
$fname = 'addon/' . $addon . '/' . $addon . '.php';
if (file_exists($fname)) {
$t = @filemtime($fname);
foreach ($installed as $i) {
if (($i['name'] == $addon) && ($i['timestamp'] != $t)) {
if (file_exists($fname)) {
$t = @filemtime($fname);
foreach ($installed as $i) {
if (($i['name'] == $addon) && ($i['timestamp'] != $t)) {
logger('Reloading addon: ' . $i['name']);
@include_once($fname);
Logger::notice("Addon {addon}: {action}", ['action' => 'reload', 'addon' => $i['name']]);
@include_once($fname);
if (function_exists($addon . '_uninstall')) {
$func = $addon . '_uninstall';
$func();
}
if (function_exists($addon . '_install')) {
$func = $addon . '_install';
$func();
}
DBA::update('addon', ['timestamp' => $t], ['id' => $i['id']]);
if (function_exists($addon . '_uninstall')) {
$func = $addon . '_uninstall';
$func(self::getApp());
}
if (function_exists($addon . '_install')) {
$func = $addon . '_install';
$func(self::getApp());
}
DBA::update('addon', ['timestamp' => $t], ['id' => $i['id']]);
}
}
}
@ -190,11 +263,14 @@ class Addon extends BaseObject
* *\endcode
* @param string $addon the name of the addon
* @return array with the addon information
* @throws \Exception
*/
public static function getInfo($addon)
{
$a = self::getApp();
$addon = Strings::sanitizeFilePathItem($addon);
$info = [
'name' => $addon,
'description' => "",
@ -210,7 +286,7 @@ class Addon extends BaseObject
$stamp1 = microtime(true);
$f = file_get_contents("addon/$addon/$addon.php");
$a->saveTimestamp($stamp1, "file");
$a->getProfiler()->saveTimestamp($stamp1, "file", System::callstack());
$r = preg_match("|/\*.*\*/|msU", $f, $m);
@ -272,13 +348,14 @@ class Addon extends BaseObject
*/
public static function saveEnabledList()
{
return Config::set("system", "addon", implode(", ", self::$addons));
return Config::set('system', 'addon', implode(',', self::$addons));
}
/**
* Returns the list of non-hidden enabled addon names
*
* @return array
* @throws \Exception
*/
public static function getVisibleList()
{
@ -296,13 +373,14 @@ class Addon extends BaseObject
/**
* Shim of Hook::register left for backward compatibility purpose.
*
* @see Hook::register
* @see Hook::register
* @deprecated since version 2018.12
* @param string $hook the name of the hook
* @param string $file the name of the file that hooks into
* @param string $function the name of the function that the hook will call
* @param int $priority A priority (defaults to 0)
* @return mixed|bool
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function registerHook($hook, $file, $function, $priority = 0)
{
@ -312,12 +390,13 @@ class Addon extends BaseObject
/**
* Shim of Hook::unregister left for backward compatibility purpose.
*
* @see Hook::unregister
* @see Hook::unregister
* @deprecated since version 2018.12
* @param string $hook the name of the hook
* @param string $file the name of the file that hooks into
* @param string $function the name of the function that the hook called
* @return boolean
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function unregisterHook($hook, $file, $function)
{
@ -327,10 +406,11 @@ class Addon extends BaseObject
/**
* Shim of Hook::callAll left for backward-compatibility purpose.
*
* @see Hook::callAll
* @see Hook::callAll
* @deprecated since version 2018.12
* @param string $name of the hook to call
* @param string $name of the hook to call
* @param string|array &$data to transmit to the callback handler
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function callHooks($name, &$data = null)
{

View file

@ -1,21 +1,18 @@
<?php
/**
* @file /src/Core/Authentication.php
*/
namespace Friendica\Core;
use Friendica\App;
use Friendica\BaseObject;
use Friendica\Core\Addon;
use Friendica\Core\Config;
use Friendica\Core\L10n;
use Friendica\Core\PConfig;
use Friendica\Database\DBA;
use Friendica\Util\DateTimeFormat;
use Friendica\Network\HTTPException\ForbiddenException;
/**
* Handle Authentification, Session and Cookies
*/
* Handle Authentification, Session and Cookies
*/
class Authentication extends BaseObject
{
/**
@ -24,19 +21,23 @@ class Authentication extends BaseObject
* @param array $user Record from "user" table
*
* @return string Hashed data
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function getCookieHashForUser($user)
{
return(hash("sha256", Config::get("system", "site_prvkey") .
$user["prvkey"] .
$user["password"]));
return hash_hmac(
"sha256",
hash_hmac("sha256", $user["password"], $user["prvkey"]),
Config::get("system", "site_prvkey")
);
}
/**
* @brief Set the "Friendica" cookie
*
* @param int $time
* @param int $time
* @param array $user Record from "user" table
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function setCookie($time, $user = [])
{
@ -45,158 +46,16 @@ class Authentication extends BaseObject
}
if ($user) {
$value = json_encode(["uid" => $user["uid"],
$value = json_encode([
"uid" => $user["uid"],
"hash" => self::getCookieHashForUser($user),
"ip" => defaults($_SERVER, 'REMOTE_ADDR', '0.0.0.0')]);
"ip" => defaults($_SERVER, 'REMOTE_ADDR', '0.0.0.0')
]);
} else {
$value = "";
}
setcookie("Friendica", $value, $time, "/", "", (Config::get('system', 'ssl_policy') == SSL_POLICY_FULL), true);
}
/**
* @brief Sets the provided user's authenticated session
*
* @todo Should be moved to Friendica\Core\Session once it's created
*
* @param type $user_record
* @param type $login_initial
* @param type $interactive
* @param type $login_refresh
*/
public static function setAuthenticatedSessionForUser($user_record, $login_initial = false, $interactive = false, $login_refresh = false)
{
$a = self::getApp();
$_SESSION['uid'] = $user_record['uid'];
$_SESSION['theme'] = $user_record['theme'];
$_SESSION['mobile-theme'] = PConfig::get($user_record['uid'], 'system', 'mobile_theme');
$_SESSION['authenticated'] = 1;
$_SESSION['page_flags'] = $user_record['page-flags'];
$_SESSION['my_url'] = $a->getbaseUrl() . '/profile/' . $user_record['nickname'];
$_SESSION['my_address'] = $user_record['nickname'] . '@' . substr($a->getbaseUrl(), strpos($a->getbaseUrl(), '://') + 3);
$_SESSION['addr'] = defaults($_SERVER, 'REMOTE_ADDR', '0.0.0.0');
$a->user = $user_record;
if ($interactive) {
if ($a->user['login_date'] <= DBA::NULL_DATETIME) {
$_SESSION['return_path'] = 'profile_photo/new';
$a->module = 'profile_photo';
info(L10n::t("Welcome ") . $a->user['username'] . EOL);
info(L10n::t('Please upload a profile photo.') . EOL);
} else {
info(L10n::t("Welcome back ") . $a->user['username'] . EOL);
}
}
$member_since = strtotime($a->user['register_date']);
if (time() < ($member_since + ( 60 * 60 * 24 * 14))) {
$_SESSION['new_member'] = true;
} else {
$_SESSION['new_member'] = false;
}
if (strlen($a->user['timezone'])) {
date_default_timezone_set($a->user['timezone']);
$a->timezone = $a->user['timezone'];
}
$master_record = $a->user;
if ((x($_SESSION, 'submanage')) && intval($_SESSION['submanage'])) {
$user = DBA::selectFirst('user', [], ['uid' => $_SESSION['submanage']]);
if (DBA::isResult($user)) {
$master_record = $user;
}
}
if ($master_record['parent-uid'] == 0) {
// First add our own entry
$a->identities = [['uid' => $master_record['uid'],
'username' => $master_record['username'],
'nickname' => $master_record['nickname']]];
// Then add all the children
$r = DBA::select('user', ['uid', 'username', 'nickname'],
['parent-uid' => $master_record['uid'], 'account_removed' => false]);
if (DBA::isResult($r)) {
$a->identities = array_merge($a->identities, DBA::toArray($r));
}
} else {
// Just ensure that the array is always defined
$a->identities = [];
// First entry is our parent
$r = DBA::select('user', ['uid', 'username', 'nickname'],
['uid' => $master_record['parent-uid'], 'account_removed' => false]);
if (DBA::isResult($r)) {
$a->identities = DBA::toArray($r);
}
// Then add all siblings
$r = DBA::select('user', ['uid', 'username', 'nickname'],
['parent-uid' => $master_record['parent-uid'], 'account_removed' => false]);
if (DBA::isResult($r)) {
$a->identities = array_merge($a->identities, DBA::toArray($r));
}
}
$r = DBA::p("SELECT `user`.`uid`, `user`.`username`, `user`.`nickname`
FROM `manage`
INNER JOIN `user` ON `manage`.`mid` = `user`.`uid`
WHERE `user`.`account_removed` = 0 AND `manage`.`uid` = ?",
$master_record['uid']
);
if (DBA::isResult($r)) {
$a->identities = array_merge($a->identities, DBA::toArray($r));
}
if ($login_initial) {
logger('auth_identities: ' . print_r($a->identities, true), LOGGER_DEBUG);
}
if ($login_refresh) {
logger('auth_identities refresh: ' . print_r($a->identities, true), LOGGER_DEBUG);
}
$contact = DBA::selectFirst('contact', [], ['uid' => $_SESSION['uid'], 'self' => true]);
if (DBA::isResult($contact)) {
$a->contact = $contact;
$a->cid = $contact['id'];
$_SESSION['cid'] = $a->cid;
}
header('X-Account-Management-Status: active; name="' . $a->user['username'] . '"; id="' . $a->user['nickname'] . '"');
if ($login_initial || $login_refresh) {
DBA::update('user', ['login_date' => DateTimeFormat::utcNow()], ['uid' => $_SESSION['uid']]);
// Set the login date for all identities of the user
DBA::update('user', ['login_date' => DateTimeFormat::utcNow()],
['parent-uid' => $master_record['uid'], 'account_removed' => false]);
}
if ($login_initial) {
/*
* If the user specified to remember the authentication, then set a cookie
* that expires after one week (the default is when the browser is closed).
* The cookie will be renewed automatically.
* The week ensures that sessions will expire after some inactivity.
*/
if (!empty($_SESSION['remember'])) {
logger('Injecting cookie for remembered user ' . $a->user['nickname']);
self::setCookie(604800, $user_record);
unset($_SESSION['remember']);
}
}
if ($login_initial) {
Addon::callHooks('logged_in', $a->user);
if (($a->module !== 'home') && isset($_SESSION['return_path'])) {
$a->internalRedirect($_SESSION['return_path']);
}
}
setcookie("Friendica", $value, $time, "/", "", (Config::get('system', 'ssl_policy') == App\BaseURL::SSL_POLICY_FULL), true);
}
/**
@ -208,5 +67,29 @@ class Authentication extends BaseObject
session_unset();
session_destroy();
}
}
public static function twoFactorCheck($uid, App $a)
{
// Check user setting, if 2FA disabled return
if (!PConfig::get($uid, '2fa', 'verified')) {
return;
}
// Check current path, if 2fa authentication module return
if ($a->argc > 0 && in_array($a->argv[0], ['2fa', 'view', 'help', 'api', 'proxy', 'logout'])) {
return;
}
// Case 1: 2FA session present and valid: return
if (Session::get('2fa')) {
return;
}
// Case 2: No valid 2FA session: redirect to code verification page
if ($a->isAjax()) {
throw new ForbiddenException();
} else {
$a->internalRedirect('2fa');
}
}
}

View file

@ -4,49 +4,33 @@
*/
namespace Friendica\Core;
use Friendica\Core\Cache\CacheDriverFactory;
use Friendica\BaseObject;
use Friendica\Core\Cache\Cache as CacheClass;
use Friendica\Core\Cache\ICache;
/**
* @brief Class for storing data for a short time
*/
class Cache extends \Friendica\BaseObject
class Cache extends BaseObject
{
const MONTH = 2592000;
const WEEK = 604800;
const DAY = 86400;
const HOUR = 3600;
const HALF_HOUR = 1800;
const QUARTER_HOUR = 900;
const FIVE_MINUTES = 300;
const MINUTE = 60;
/**
* @var Cache\ICacheDriver
*/
private static $driver = null;
public static $driver_class = null;
public static $driver_name = null;
public static function init()
{
self::$driver_name = Config::get('system', 'cache_driver', 'database');
self::$driver = CacheDriverFactory::create(self::$driver_name);
self::$driver_class = get_class(self::$driver);
}
/**
* Returns the current cache driver
*
* @return Cache\ICacheDriver
*/
private static function getDriver()
{
if (self::$driver === null) {
self::init();
}
return self::$driver;
}
/** @deprecated Use CacheClass::MONTH */
const MONTH = CacheClass::MONTH;
/** @deprecated Use CacheClass::WEEK */
const WEEK = CacheClass::WEEK;
/** @deprecated Use CacheClass::DAY */
const DAY = CacheClass::DAY;
/** @deprecated Use CacheClass::HOUR */
const HOUR = CacheClass::HOUR;
/** @deprecated Use CacheClass::HALF_HOUR */
const HALF_HOUR = CacheClass::HALF_HOUR;
/** @deprecated Use CacheClass::QUARTER_HOUR */
const QUARTER_HOUR = CacheClass::QUARTER_HOUR;
/** @deprecated Use CacheClass::FIVE_MINUTES */
const FIVE_MINUTES = CacheClass::FIVE_MINUTES;
/** @deprecated Use CacheClass::MINUTE */
const MINUTE = CacheClass::MINUTE;
/** @deprecated Use CacheClass::INFINITE */
const INFINITE = CacheClass::INFINITE;
/**
* @brief Returns all the cache keys sorted alphabetically
@ -54,16 +38,11 @@ class Cache extends \Friendica\BaseObject
* @param string $prefix Prefix of the keys (optional)
*
* @return array Empty if the driver doesn't support this feature
* @throws \Exception
*/
public static function getAllKeys($prefix = null)
{
$time = microtime(true);
$return = self::getDriver()->getAllKeys($prefix);
self::getApp()->saveTimestamp($time, 'cache');
return $return;
return self::getClass(ICache::class)->getAllKeys($prefix);
}
/**
@ -72,16 +51,11 @@ class Cache extends \Friendica\BaseObject
* @param string $key The key to the cached data
*
* @return mixed Cached $value or "null" if not found
* @throws \Exception
*/
public static function get($key)
{
$time = microtime(true);
$return = self::getDriver()->get($key);
self::getApp()->saveTimestamp($time, 'cache');
return $return;
return self::getClass(ICache::class)->get($key);
}
/**
@ -94,16 +68,11 @@ class Cache extends \Friendica\BaseObject
* @param integer $duration The cache lifespan
*
* @return bool
* @throws \Exception
*/
public static function set($key, $value, $duration = self::MONTH)
public static function set($key, $value, $duration = CacheClass::MONTH)
{
$time = microtime(true);
$return = self::getDriver()->set($key, $value, $duration);
self::getApp()->saveTimestamp($time, 'cache_write');
return $return;
return self::getClass(ICache::class)->set($key, $value, $duration);
}
/**
@ -112,16 +81,11 @@ class Cache extends \Friendica\BaseObject
* @param string $key The key to the cached data
*
* @return bool
* @throws \Exception
*/
public static function delete($key)
{
$time = microtime(true);
$return = self::getDriver()->delete($key);
self::getApp()->saveTimestamp($time, 'cache_write');
return $return;
return self::getClass(ICache::class)->delete($key);
}
/**
@ -129,10 +93,11 @@ class Cache extends \Friendica\BaseObject
*
* @param boolean $outdated just remove outdated values
*
* @return void
* @return bool
* @throws \Exception
*/
public static function clear($outdated = true)
{
return self::getDriver()->clear($outdated);
return self::getClass(ICache::class)->clear($outdated);
}
}

View file

@ -0,0 +1,163 @@
<?php
namespace Friendica\Core\Cache;
use Exception;
/**
* APCu Cache.
*
* @author Philipp Holzer <admin@philipp.info>
*/
class APCuCache extends Cache implements IMemoryCache
{
use TraitCompareSet;
use TraitCompareDelete;
/**
* @throws Exception
*/
public function __construct(string $hostname)
{
if (!self::isAvailable()) {
throw new Exception('APCu is not available.');
}
parent::__construct($hostname);
}
/**
* (@inheritdoc)
*/
public function getAllKeys($prefix = null)
{
$ns = $this->getCacheKey($prefix);
$ns = preg_quote($ns, '/');
if (class_exists('\APCIterator')) {
$iterator = new \APCIterator('user', '/^' . $ns. '/', APC_ITER_KEY);
} else {
$iterator = new \APCUIterator('/^' . $ns . '/', APC_ITER_KEY);
}
$keys = [];
foreach ($iterator as $item) {
array_push($keys, $item['key']);
}
return $this->getOriginalKeys($keys);
}
/**
* (@inheritdoc)
*/
public function get($key)
{
$return = null;
$cachekey = $this->getCacheKey($key);
$cached = apcu_fetch($cachekey, $success);
if (!$success) {
return null;
}
$value = unserialize($cached);
// Only return a value if the serialized value is valid.
// We also check if the db entry is a serialized
// boolean 'false' value (which we want to return).
if ($cached === serialize(false) || $value !== false) {
$return = $value;
}
return $return;
}
/**
* (@inheritdoc)
*/
public function set($key, $value, $ttl = Cache::FIVE_MINUTES)
{
$cachekey = $this->getCacheKey($key);
$cached = serialize($value);
if ($ttl > 0) {
return apcu_store(
$cachekey,
$cached,
$ttl
);
} else {
return apcu_store(
$cachekey,
$cached
);
}
}
/**
* (@inheritdoc)
*/
public function delete($key)
{
$cachekey = $this->getCacheKey($key);
return apcu_delete($cachekey);
}
/**
* (@inheritdoc)
*/
public function clear($outdated = true)
{
if ($outdated) {
return true;
} else {
$prefix = $this->getPrefix();
$prefix = preg_quote($prefix, '/');
if (class_exists('\APCIterator')) {
$iterator = new \APCIterator('user', '/^' . $prefix . '/', APC_ITER_KEY);
} else {
$iterator = new \APCUIterator('/^' . $prefix . '/', APC_ITER_KEY);
}
return apcu_delete($iterator);
}
}
/**
* (@inheritdoc)
*/
public function add($key, $value, $ttl = Cache::FIVE_MINUTES)
{
$cachekey = $this->getCacheKey($key);
$cached = serialize($value);
return apcu_add($cachekey, $cached);
}
public static function isAvailable()
{
if (!extension_loaded('apcu')) {
return false;
} elseif (!ini_get('apc.enabled') && !ini_get('apc.enable_cli')) {
return false;
} elseif (
version_compare(phpversion('apc') ?: '0.0.0', '4.0.6') === -1 &&
version_compare(phpversion('apcu') ?: '0.0.0', '5.1.0') === -1
) {
return false;
}
return true;
}
/**
* {@inheritDoc}
*/
public function getName()
{
return self::TYPE_APCU;
}
}

View file

@ -1,72 +0,0 @@
<?php
namespace Friendica\Core\Cache;
use Friendica\BaseObject;
/**
* Abstract class for common used functions
*
* Class AbstractCacheDriver
*
* @package Friendica\Core\Cache
*/
abstract class AbstractCacheDriver extends BaseObject
{
/**
* @param string $key The original key
* @return string The cache key used for the cache
*/
protected function getCacheKey($key)
{
// We fetch with the hostname as key to avoid problems with other applications
return self::getApp()->getHostName() . ":" . $key;
}
/**
* @param array $keys A list of cached keys
* @return array A list of original keys
*/
protected function getOriginalKeys($keys)
{
if (empty($keys)) {
return [];
} else {
// Keys are prefixed with the node hostname, let's remove it
array_walk($keys, function (&$value) {
$value = preg_replace('/^' . self::getApp()->getHostName() . ':/', '', $value);
});
sort($keys);
return $keys;
}
}
/**
* Filters the keys of an array with a given prefix
* Returns the filtered keys as an new array
*
* @param array $array The array, which should get filtered
* @param string|null $prefix The prefix (if null, all keys will get returned)
*
* @return array The filtered array with just the keys
*/
protected function filterArrayKeysByPrefix($array, $prefix = null)
{
if (empty($prefix)) {
return array_keys($array);
} else {
$result = [];
foreach (array_keys($array) as $key) {
if (strpos($key, $prefix) === 0) {
array_push($result, $key);
}
}
return $result;
}
}
}

View file

@ -2,17 +2,14 @@
namespace Friendica\Core\Cache;
use Friendica\Core\Cache;
/**
* Implementation of the IMemoryCacheDriver mainly for testing purpose
* Implementation of the IMemoryCache mainly for testing purpose
*
* Class ArrayCache
*
* @package Friendica\Core\Cache
*/
class ArrayCache extends AbstractCacheDriver implements IMemoryCacheDriver
class ArrayCache extends Cache implements IMemoryCache
{
use TraitCompareDelete;
@ -24,7 +21,7 @@ class ArrayCache extends AbstractCacheDriver implements IMemoryCacheDriver
*/
public function getAllKeys($prefix = null)
{
return $this->filterArrayKeysByPrefix($this->cachedData, $prefix);
return $this->filterArrayKeysByPrefix(array_keys($this->cachedData), $prefix);
}
/**
@ -93,4 +90,12 @@ class ArrayCache extends AbstractCacheDriver implements IMemoryCacheDriver
return false;
}
}
/**
* {@inheritDoc}
*/
public function getName()
{
return self::TYPE_ARRAY;
}
}

108
src/Core/Cache/Cache.php Normal file
View file

@ -0,0 +1,108 @@
<?php
namespace Friendica\Core\Cache;
/**
* Abstract class for common used functions
*
* Class AbstractCache
*
* @package Friendica\Core\Cache
*/
abstract class Cache implements ICache
{
const TYPE_APCU = 'apcu';
const TYPE_ARRAY = 'array';
const TYPE_DATABASE = 'database';
const TYPE_MEMCACHE = 'memcache';
const TYPE_MEMCACHED = 'memcached';
const TYPE_REDIS = 'redis';
const MONTH = 2592000;
const WEEK = 604800;
const DAY = 86400;
const HOUR = 3600;
const HALF_HOUR = 1800;
const QUARTER_HOUR = 900;
const FIVE_MINUTES = 300;
const MINUTE = 60;
const INFINITE = 0;
/**
* @var string The hostname
*/
private $hostName;
public function __construct(string $hostName)
{
$this->hostName = $hostName;
}
/**
* Returns the prefix (to avoid namespace conflicts)
*
* @return string
* @throws \Exception
*/
protected function getPrefix()
{
// We fetch with the hostname as key to avoid problems with other applications
return $this->hostName;
}
/**
* @param string $key The original key
* @return string The cache key used for the cache
* @throws \Exception
*/
protected function getCacheKey($key)
{
return $this->getPrefix() . ":" . $key;
}
/**
* @param array $keys A list of cached keys
* @return array A list of original keys
*/
protected function getOriginalKeys($keys)
{
if (empty($keys)) {
return [];
} else {
// Keys are prefixed with the node hostname, let's remove it
array_walk($keys, function (&$value) {
$value = preg_replace('/^' . $this->hostName . ':/', '', $value);
});
sort($keys);
return $keys;
}
}
/**
* Filters the keys of an array with a given prefix
* Returns the filtered keys as an new array
*
* @param array $keys The keys, which should get filtered
* @param string|null $prefix The prefix (if null, all keys will get returned)
*
* @return array The filtered array with just the keys
*/
protected function filterArrayKeysByPrefix(array $keys, string $prefix = null)
{
if (empty($prefix)) {
return $keys;
} else {
$result = [];
foreach ($keys as $key) {
if (strpos($key, $prefix) === 0) {
array_push($result, $key);
}
}
return $result;
}
}
}

View file

@ -1,48 +0,0 @@
<?php
namespace Friendica\Core\Cache;
use Friendica\Core\Config;
/**
* Class CacheDriverFactory
*
* @package Friendica\Core\Cache
*
* A basic class to generate a CacheDriver
*/
class CacheDriverFactory
{
/**
* This method creates a CacheDriver for the given cache driver name
*
* @param string $driver The name of the cache driver
* @return ICacheDriver The instance of the CacheDriver
* @throws \Exception The exception if something went wrong during the CacheDriver creation
*/
public static function create($driver) {
switch ($driver) {
case 'memcache':
$memcache_host = Config::get('system', 'memcache_host');
$memcache_port = Config::get('system', 'memcache_port');
return new MemcacheCacheDriver($memcache_host, $memcache_port);
break;
case 'memcached':
$memcached_hosts = Config::get('system', 'memcached_hosts');
return new MemcachedCacheDriver($memcached_hosts);
break;
case 'redis':
$redis_host = Config::get('system', 'redis_host');
$redis_port = Config::get('system', 'redis_port');
return new RedisCacheDriver($redis_host, $redis_port);
break;
default:
return new DatabaseCacheDriver();
}
}
}

View file

@ -0,0 +1,120 @@
<?php
namespace Friendica\Core\Cache;
use Friendica\Database\Database;
use Friendica\Util\DateTimeFormat;
/**
* Database Cache
*
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class DatabaseCache extends Cache implements ICache
{
/**
* @var Database
*/
private $dba;
public function __construct(string $hostname, Database $dba)
{
parent::__construct($hostname);
$this->dba = $dba;
}
/**
* (@inheritdoc)
*/
public function getAllKeys($prefix = null)
{
if (empty($prefix)) {
$where = ['`expires` >= ?', DateTimeFormat::utcNow()];
} else {
$where = ['`expires` >= ? AND `k` LIKE CONCAT(?, \'%\')', DateTimeFormat::utcNow(), $prefix];
}
$stmt = $this->dba->select('cache', ['k'], $where);
$keys = [];
while ($key = $this->dba->fetch($stmt)) {
array_push($keys, $key['k']);
}
$this->dba->close($stmt);
return $keys;
}
/**
* (@inheritdoc)
*/
public function get($key)
{
$cache = $this->dba->selectFirst('cache', ['v'], ['`k` = ? AND (`expires` >= ? OR `expires` = -1)', $key, DateTimeFormat::utcNow()]);
if ($this->dba->isResult($cache)) {
$cached = $cache['v'];
$value = @unserialize($cached);
// Only return a value if the serialized value is valid.
// We also check if the db entry is a serialized
// boolean 'false' value (which we want to return).
if ($cached === serialize(false) || $value !== false) {
return $value;
}
}
return null;
}
/**
* (@inheritdoc)
*/
public function set($key, $value, $ttl = Cache::FIVE_MINUTES)
{
if ($ttl > 0) {
$fields = [
'v' => serialize($value),
'expires' => DateTimeFormat::utc('now + ' . $ttl . 'seconds'),
'updated' => DateTimeFormat::utcNow()
];
} else {
$fields = [
'v' => serialize($value),
'expires' => -1,
'updated' => DateTimeFormat::utcNow()
];
}
return $this->dba->update('cache', $fields, ['k' => $key], true);
}
/**
* (@inheritdoc)
*/
public function delete($key)
{
return $this->dba->delete('cache', ['k' => $key]);
}
/**
* (@inheritdoc)
*/
public function clear($outdated = true)
{
if ($outdated) {
return $this->dba->delete('cache', ['`expires` < NOW()']);
} else {
return $this->dba->delete('cache', ['`k` IS NOT NULL ']);
}
}
/**
* {@inheritDoc}
*/
public function getName()
{
return self::TYPE_DATABASE;
}
}

View file

@ -1,93 +0,0 @@
<?php
namespace Friendica\Core\Cache;
use Friendica\Core\Cache;
use Friendica\Database\DBA;
use Friendica\Util\DateTimeFormat;
/**
* Database Cache Driver
*
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class DatabaseCacheDriver extends AbstractCacheDriver implements ICacheDriver
{
/**
* (@inheritdoc)
*/
public function getAllKeys($prefix = null)
{
if (empty($prefix)) {
$where = ['`expires` >= ?', DateTimeFormat::utcNow()];
} else {
$where = ['`expires` >= ? AND `k` LIKE CONCAT(?, \'%\')', DateTimeFormat::utcNow(), $prefix];
}
$stmt = DBA::select('cache', ['k'], $where);
$keys = [];
while ($key = DBA::fetch($stmt)) {
array_push($keys, $key['k']);
}
DBA::close($stmt);
return $keys;
}
/**
* (@inheritdoc)
*/
public function get($key)
{
$cache = DBA::selectFirst('cache', ['v'], ['`k` = ? AND `expires` >= ?', $key, DateTimeFormat::utcNow()]);
if (DBA::isResult($cache)) {
$cached = $cache['v'];
$value = @unserialize($cached);
// Only return a value if the serialized value is valid.
// We also check if the db entry is a serialized
// boolean 'false' value (which we want to return).
if ($cached === serialize(false) || $value !== false) {
return $value;
}
}
return null;
}
/**
* (@inheritdoc)
*/
public function set($key, $value, $ttl = Cache::FIVE_MINUTES)
{
$fields = [
'v' => serialize($value),
'expires' => DateTimeFormat::utc('now + ' . $ttl . 'seconds'),
'updated' => DateTimeFormat::utcNow()
];
return DBA::update('cache', $fields, ['k' => $key], true);
}
/**
* (@inheritdoc)
*/
public function delete($key)
{
return DBA::delete('cache', ['k' => $key]);
}
/**
* (@inheritdoc)
*/
public function clear($outdated = true)
{
if ($outdated) {
return DBA::delete('cache', ['`expires` < NOW()']);
} else {
return DBA::delete('cache', ['`k` IS NOT NULL ']);
}
}
}

View file

@ -2,14 +2,12 @@
namespace Friendica\Core\Cache;
use Friendica\Core\Cache;
/**
* Cache Driver Interface
* Cache Interface
*
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
interface ICacheDriver
interface ICache
{
/**
* Lists all cache keys
@ -34,7 +32,7 @@ interface ICacheDriver
*
* @param string $key The cache key
* @param mixed $value The value to store
* @param integer $ttl The cache lifespan, must be one of the Cache constants
* @param integer $ttl The cache lifespan, must be one of the Cache constants
*
* @return bool
*/
@ -56,4 +54,11 @@ interface ICacheDriver
* @return bool
*/
public function clear($outdated = true);
/**
* Returns the name of the current cache
*
* @return string
*/
public function getName();
}

View file

@ -1,16 +1,15 @@
<?php
namespace Friendica\Core\Cache;
use Friendica\Core\Cache;
/**
* This interface defines methods for Memory-Caches only
*
* Interface IMemoryCacheDriver
* Interface IMemoryCache
*
* @package Friendica\Core\Cache
*/
interface IMemoryCacheDriver extends ICacheDriver
interface IMemoryCache extends ICache
{
/**
* Sets a value if it's not already stored
@ -28,7 +27,7 @@ interface IMemoryCacheDriver extends ICacheDriver
* @param string $key The cache key
* @param mixed $oldValue The old value we know from the cache
* @param mixed $newValue The new value we want to set
* @param int $ttl The cache lifespan, must be one of the Cache constants
* @param int $ttl The cache lifespan, must be one of the Cache constants
*
* @return bool
*/

View file

@ -2,20 +2,20 @@
namespace Friendica\Core\Cache;
use Friendica\Core\Cache;
use Exception;
use Friendica\Core\Config\Configuration;
use Memcache;
/**
* Memcache Cache Driver
* Memcache Cache
*
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class MemcacheCacheDriver extends AbstractCacheDriver implements IMemoryCacheDriver
class MemcacheCache extends Cache implements IMemoryCache
{
use TraitCompareSet;
use TraitCompareDelete;
use TraitMemcacheCommand;
/**
* @var Memcache
@ -23,20 +23,23 @@ class MemcacheCacheDriver extends AbstractCacheDriver implements IMemoryCacheDri
private $memcache;
/**
* @param string $memcache_host
* @param int $memcache_port
* @throws Exception
*/
public function __construct($memcache_host, $memcache_port)
public function __construct(string $hostname, Configuration $config)
{
if (!class_exists('Memcache', false)) {
throw new Exception('Memcache class isn\'t available');
}
parent::__construct($hostname);
$this->memcache = new Memcache();
if (!$this->memcache->connect($memcache_host, $memcache_port)) {
throw new Exception('Expected Memcache server at ' . $memcache_host . ':' . $memcache_port . ' isn\'t available');
$this->server = $config->get('system', 'memcache_host');;
$this->port = $config->get('system', 'memcache_port');
if (!@$this->memcache->connect($this->server, $this->port)) {
throw new Exception('Expected Memcache server at ' . $this->server . ':' . $this->port . ' isn\'t available');
}
}
@ -45,21 +48,7 @@ class MemcacheCacheDriver extends AbstractCacheDriver implements IMemoryCacheDri
*/
public function getAllKeys($prefix = null)
{
$keys = [];
$allSlabs = $this->memcache->getExtendedStats('slabs');
foreach ($allSlabs as $slabs) {
foreach (array_keys($slabs) as $slabId) {
$cachedump = $this->memcache->getExtendedStats('cachedump', (int)$slabId);
foreach ($cachedump as $key => $arrVal) {
if (!is_array($arrVal)) {
continue;
}
$keys = array_merge($keys, array_keys($arrVal));
}
}
}
$keys = $this->getOriginalKeys($keys);
$keys = $this->getOriginalKeys($this->getMemcacheKeys());
return $this->filterArrayKeysByPrefix($keys, $prefix);
}
@ -69,7 +58,7 @@ class MemcacheCacheDriver extends AbstractCacheDriver implements IMemoryCacheDri
*/
public function get($key)
{
$return = null;
$return = null;
$cachekey = $this->getCacheKey($key);
// We fetch with the hostname as key to avoid problems with other applications
@ -145,4 +134,12 @@ class MemcacheCacheDriver extends AbstractCacheDriver implements IMemoryCacheDri
$cachekey = $this->getCacheKey($key);
return $this->memcache->add($cachekey, serialize($value), MEMCACHE_COMPRESSED, $ttl);
}
/**
* {@inheritDoc}
*/
public function getName()
{
return self::TYPE_MEMCACHE;
}
}

View file

@ -2,27 +2,32 @@
namespace Friendica\Core\Cache;
use Friendica\Core\Cache;
use Exception;
use Friendica\Network\HTTPException\InternalServerErrorException;
use Friendica\Core\Config\Configuration;
use Memcached;
use Psr\Log\LoggerInterface;
/**
* Memcached Cache Driver
* Memcached Cache
*
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class MemcachedCacheDriver extends AbstractCacheDriver implements IMemoryCacheDriver
class MemcachedCache extends Cache implements IMemoryCache
{
use TraitCompareSet;
use TraitCompareDelete;
use TraitMemcacheCommand;
/**
* @var \Memcached
*/
private $memcached;
/**
* @var LoggerInterface
*/
private $logger;
/**
* Due to limitations of the INI format, the expected configuration for Memcached servers is the following:
* array {
@ -31,22 +36,32 @@ class MemcachedCacheDriver extends AbstractCacheDriver implements IMemoryCacheDr
* }
*
* @param array $memcached_hosts
*
* @throws \Exception
*/
public function __construct(array $memcached_hosts)
public function __construct(string $hostname, Configuration $config, LoggerInterface $logger)
{
if (!class_exists('Memcached', false)) {
throw new Exception('Memcached class isn\'t available');
}
parent::__construct($hostname);
$this->logger = $logger;
$this->memcached = new Memcached();
$memcached_hosts = $config->get('system', 'memcached_hosts');
array_walk($memcached_hosts, function (&$value) {
if (is_string($value)) {
$value = array_map('trim', explode(',', $value));
}
});
$this->server = $memcached_hosts[0][0] ?? 'localhost';
$this->port = $memcached_hosts[0][1] ?? 11211;
$this->memcached->addServers($memcached_hosts);
if (count($this->memcached->getServerList()) == 0) {
@ -59,14 +74,9 @@ class MemcachedCacheDriver extends AbstractCacheDriver implements IMemoryCacheDr
*/
public function getAllKeys($prefix = null)
{
$keys = $this->getOriginalKeys($this->memcached->getAllKeys());
$keys = $this->getOriginalKeys($this->getMemcacheKeys());
if ($this->memcached->getResultCode() == Memcached::RES_SUCCESS) {
return $this->filterArrayKeysByPrefix($keys, $prefix);
} else {
logger('Memcached \'getAllKeys\' failed with ' . $this->memcached->getResultMessage(), LOGGER_ALL);
return [];
}
return $this->filterArrayKeysByPrefix($keys, $prefix);
}
/**
@ -74,7 +84,7 @@ class MemcachedCacheDriver extends AbstractCacheDriver implements IMemoryCacheDr
*/
public function get($key)
{
$return = null;
$return = null;
$cachekey = $this->getCacheKey($key);
// We fetch with the hostname as key to avoid problems with other applications
@ -83,7 +93,7 @@ class MemcachedCacheDriver extends AbstractCacheDriver implements IMemoryCacheDr
if ($this->memcached->getResultCode() === Memcached::RES_SUCCESS) {
$return = $value;
} else {
logger('Memcached \'get\' failed with ' . $this->memcached->getResultMessage(), LOGGER_ALL);
$this->logger->debug('Memcached \'get\' failed', ['result' => $this->memcached->getResultMessage()]);
}
return $return;
@ -140,4 +150,12 @@ class MemcachedCacheDriver extends AbstractCacheDriver implements IMemoryCacheDr
$cachekey = $this->getCacheKey($key);
return $this->memcached->add($cachekey, $value, $ttl);
}
/**
* {@inheritDoc}
*/
public function getName()
{
return self::TYPE_MEMCACHED;
}
}

View file

@ -0,0 +1,162 @@
<?php
namespace Friendica\Core\Cache;
use Friendica\Core\System;
use Friendica\Util\Profiler;
/**
* This class wraps cache driver so they can get profiled - in case the profiler is enabled
*
* It is using the decorator pattern (@see
*/
class ProfilerCache implements ICache, IMemoryCache
{
/**
* @var ICache The original cache driver
*/
private $cache;
/**
* @var Profiler The profiler of Friendica
*/
private $profiler;
public function __construct(ICache $cache, Profiler $profiler)
{
$this->cache = $cache;
$this->profiler = $profiler;
}
/**
* {@inheritDoc}
*/
public function getAllKeys($prefix = null)
{
$time = microtime(true);
$return = $this->cache->getAllKeys($prefix);
$this->profiler->saveTimestamp($time, 'cache', System::callstack());
return $return;
}
/**
* {@inheritDoc}
*/
public function get($key)
{
$time = microtime(true);
$return = $this->cache->get($key);
$this->profiler->saveTimestamp($time, 'cache', System::callstack());
return $return;
}
/**
* {@inheritDoc}
*/
public function set($key, $value, $ttl = Cache::FIVE_MINUTES)
{
$time = microtime(true);
$return = $this->cache->set($key, $value, $ttl);
$this->profiler->saveTimestamp($time, 'cache', System::callstack());
return $return;
}
/**
* {@inheritDoc}
*/
public function delete($key)
{
$time = microtime(true);
$return = $this->cache->delete($key);
$this->profiler->saveTimestamp($time, 'cache', System::callstack());
return $return;
}
/**
* {@inheritDoc}
*/
public function clear($outdated = true)
{
$time = microtime(true);
$return = $this->cache->clear($outdated);
$this->profiler->saveTimestamp($time, 'cache', System::callstack());
return $return;
}
/**
* {@inheritDoc}
*/
public function add($key, $value, $ttl = Cache::FIVE_MINUTES)
{
if ($this->cache instanceof IMemoryCache) {
$time = microtime(true);
$return = $this->cache->add($key, $value, $ttl);
$this->profiler->saveTimestamp($time, 'cache', System::callstack());
return $return;
} else {
return false;
}
}
/**
* {@inheritDoc}
*/
public function compareSet($key, $oldValue, $newValue, $ttl = Cache::FIVE_MINUTES)
{
if ($this->cache instanceof IMemoryCache) {
$time = microtime(true);
$return = $this->cache->compareSet($key, $oldValue, $newValue, $ttl);
$this->profiler->saveTimestamp($time, 'cache', System::callstack());
return $return;
} else {
return false;
}
}
/**
* {@inheritDoc}
*/
public function compareDelete($key, $value)
{
if ($this->cache instanceof IMemoryCache) {
$time = microtime(true);
$return = $this->cache->compareDelete($key, $value);
$this->profiler->saveTimestamp($time, 'cache', System::callstack());
return $return;
} else {
return false;
}
}
/**
* {@inheritDoc}
*/
public function GetName()
{
return $this->cache->getName() . ' (with profiler)';
}
}

View file

@ -2,18 +2,17 @@
namespace Friendica\Core\Cache;
use Friendica\Core\Cache;
use Exception;
use Friendica\Core\Config\Configuration;
use Redis;
/**
* Redis Cache Driver. This driver is based on Memcache driver
* Redis Cache. This driver is based on Memcache driver
*
* @author Hypolite Petovan <hypolite@mrpetovan.com>
* @author Roland Haeder <roland@mxchange.org>
*/
class RedisCacheDriver extends AbstractCacheDriver implements IMemoryCacheDriver
class RedisCache extends Cache implements IMemoryCache
{
/**
* @var Redis
@ -21,20 +20,35 @@ class RedisCacheDriver extends AbstractCacheDriver implements IMemoryCacheDriver
private $redis;
/**
* @param string $redis_host
* @param int $redis_port
* @throws Exception
*/
public function __construct($redis_host, $redis_port)
public function __construct(string $hostname, Configuration $config)
{
if (!class_exists('Redis', false)) {
throw new Exception('Redis class isn\'t available');
}
parent::__construct($hostname);
$this->redis = new Redis();
if (!$this->redis->connect($redis_host, $redis_port)) {
$redis_host = $config->get('system', 'redis_host');
$redis_port = $config->get('system', 'redis_port');
$redis_pw = $config->get('system', 'redis_password');
$redis_db = $config->get('system', 'redis_db', 0);
if (isset($redis_port) && !@$this->redis->connect($redis_host, $redis_port)) {
throw new Exception('Expected Redis server at ' . $redis_host . ':' . $redis_port . ' isn\'t available');
} elseif (!@$this->redis->connect($redis_host)) {
throw new Exception('Expected Redis server at ' . $redis_host . ' isn\'t available');
}
if (isset($redis_pw) && !$this->redis->auth($redis_pw)) {
throw new Exception('Cannot authenticate redis server at ' . $redis_host . ':' . $redis_port);
}
if ($redis_db !== 0 && !$this->redis->select($redis_db)) {
throw new Exception('Cannot switch to redis db ' . $redis_db . ' at ' . $redis_host . ':' . $redis_port);
}
}
@ -108,7 +122,7 @@ class RedisCacheDriver extends AbstractCacheDriver implements IMemoryCacheDriver
public function delete($key)
{
$cachekey = $this->getCacheKey($key);
return ($this->redis->delete($cachekey) > 0);
return ($this->redis->del($cachekey) > 0);
}
/**
@ -152,7 +166,7 @@ class RedisCacheDriver extends AbstractCacheDriver implements IMemoryCacheDriver
->exec();
} else {
$result = $this->redis->multi()
->set($cachekey, $newValue)
->set($cachekey, $newCached)
->exec();
}
return $result !== false;
@ -179,4 +193,12 @@ class RedisCacheDriver extends AbstractCacheDriver implements IMemoryCacheDriver
$this->redis->unwatch();
return false;
}
/**
* {@inheritDoc}
*/
public function getName()
{
return self::TYPE_REDIS;
}
}

View file

@ -2,8 +2,6 @@
namespace Friendica\Core\Cache;
use Friendica\Core\Cache;
/**
* Trait TraitCompareSetDelete
*

View file

@ -2,8 +2,6 @@
namespace Friendica\Core\Cache;
use Friendica\Core\Cache;
/**
* Trait TraitCompareSetDelete
*

View file

@ -0,0 +1,104 @@
<?php
namespace Friendica\Core\Cache;
use Friendica\Network\HTTPException\InternalServerErrorException;
/**
* Trait for Memcache to add a custom version of the
* method getAllKeys() since this isn't working anymore
*
* Adds the possibility to directly communicate with the memcache too
*/
trait TraitMemcacheCommand
{
/**
* @var string server address
*/
protected $server;
/**
* @var int server port
*/
protected $port;
/**
* Retrieves the stored keys of the memcache instance
* Uses custom commands, which aren't bound to the used instance of the class
*
* @todo Due the fact that we use a custom command, there are race conditions possible:
* - $this->memcache(d) adds a key
* - $this->getMemcacheKeys is called directly "after"
* - But $this->memcache(d) isn't finished adding the key, so getMemcacheKeys doesn't find it
*
* @return array All keys of the memcache instance
*
* @throws InternalServerErrorException
*/
protected function getMemcacheKeys()
{
$string = $this->sendMemcacheCommand("stats items");
$lines = explode("\r\n", $string);
$slabs = [];
$keys = [];
foreach ($lines as $line) {
if (preg_match("/STAT items:([\d]+):number ([\d]+)/", $line, $matches) &&
isset($matches[1]) &&
!in_array($matches[1], $keys)) {
$slabs[] = $matches[1];
$string = $this->sendMemcacheCommand("stats cachedump " . $matches[1] . " " . $matches[2]);
preg_match_all("/ITEM (.*?) /", $string, $matches);
$keys = array_merge($keys, $matches[1]);
}
}
return $keys;
}
/**
* Taken directly from memcache PECL source
* Sends a command to the memcache instance and returns the result
* as a string
*
* http://pecl.php.net/package/memcache
*
* @param string $command The command to send to the Memcache server
*
* @return string The returned buffer result
*
* @throws InternalServerErrorException In case the memcache server isn't available (anymore)
*/
protected function sendMemcacheCommand(string $command)
{
$s = @fsockopen($this->server, $this->port);
if (!$s) {
throw new InternalServerErrorException("Cant connect to:" . $this->server . ':' . $this->port);
}
fwrite($s, $command . "\r\n");
$buf = '';
while (!feof($s)) {
$buf .= fgets($s, 256);
if (strpos($buf, "END\r\n") !== false) { // stat says end
break;
}
if (strpos($buf, "DELETED\r\n") !== false || strpos($buf, "NOT_FOUND\r\n") !== false) { // delete says these
break;
}
if (strpos($buf, "OK\r\n") !== false) { // flush_all says ok
break;
}
}
fclose($s);
return ($buf);
}
}

View file

@ -8,11 +8,8 @@
*/
namespace Friendica\Core;
use Friendica\App;
use Friendica\BaseObject;
use Friendica\Core\Config;
require_once 'include/dba.php';
use Friendica\Core\Config\Configuration;
/**
* @brief Arbitrary system configuration storage
@ -23,132 +20,62 @@ require_once 'include/dba.php';
*/
class Config extends BaseObject
{
/**
* @var Friendica\Core\Config\IConfigAdapter
*/
private static $adapter = null;
public static function init()
{
// Database isn't ready or populated yet
if (!self::getApp()->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
return;
}
if (self::getApp()->getConfigValue('system', 'config_adapter') == 'preload') {
self::$adapter = new Config\PreloadConfigAdapter();
} else {
self::$adapter = new Config\JITConfigAdapter();
}
}
/**
* @brief Loads all configuration values of family into a cached storage.
*
* All configuration values of the system are stored in global cache
* which is available under the global variable $a->config
*
* @param string $family The category of the configuration value
* @param string $cat The category of the configuration value
*
* @return void
*/
public static function load($family = "config")
public static function load($cat = "config")
{
// Database isn't ready or populated yet
if (!self::getApp()->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
return;
}
if (empty(self::$adapter)) {
self::init();
}
self::$adapter->load($family);
self::getClass(Configuration::class)->load($cat);
}
/**
* @brief Get a particular user's config variable given the category name
* ($family) and a key.
*
* Get a particular config value from the given category ($family)
* and the $key from a cached storage in $a->config[$uid].
* $instore is only used by the set_config function
* to determine if the key already exists in the DB
* If a key is found in the DB but doesn't exist in
* local config cache, pull it into the cache so we don't have
* to hit the DB again for this item.
*
* @param string $family The category of the configuration value
* @param string $cat The category of the configuration value
* @param string $key The configuration key to query
* @param mixed $default_value optional, The value to return if key is not set (default: null)
* @param boolean $refresh optional, If true the config is loaded from the db and not from the cache (default: false)
*
* @return mixed Stored value or null if it does not exist
*/
public static function get($family, $key, $default_value = null, $refresh = false)
public static function get($cat, $key, $default_value = null, $refresh = false)
{
// Database isn't ready or populated yet, fallback to file config
if (!self::getApp()->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
return self::getApp()->getConfigValue($family, $key, $default_value);
}
if (empty(self::$adapter)) {
self::init();
}
return self::$adapter->get($family, $key, $default_value, $refresh);
return self::getClass(Configuration::class)->get($cat, $key, $default_value, $refresh);
}
/**
* @brief Sets a configuration value for system config
*
* Stores a config value ($value) in the category ($family) under the key ($key)
* for the user_id $uid.
* Stores a config value ($value) in the category ($cat) under the key ($key)
*
* Note: Please do not store booleans - convert to 0/1 integer values!
*
* @param string $family The category of the configuration value
* @param string $cat The category of the configuration value
* @param string $key The configuration key to set
* @param mixed $value The value to store
*
* @return bool Operation success
*/
public static function set($family, $key, $value)
public static function set($cat, $key, $value)
{
// Database isn't ready or populated yet
if (!self::getApp()->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
return false;
}
if (empty(self::$adapter)) {
self::init();
}
return self::$adapter->set($family, $key, $value);
return self::getClass(Configuration::class)->set($cat, $key, $value);
}
/**
* @brief Deletes the given key from the system configuration.
*
* Removes the configured value from the stored cache in $a->config
* and removes it from the database.
*
* @param string $family The category of the configuration value
* @param string $cat The category of the configuration value
* @param string $key The configuration key to delete
*
* @return mixed
* @return bool
*/
public static function delete($family, $key)
public static function delete($cat, $key)
{
// Database isn't ready or populated yet
if (!self::getApp()->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
return false;
}
if (empty(self::$adapter)) {
self::init();
}
return self::$adapter->delete($family, $key);
return self::getClass(Configuration::class)->delete($cat, $key);
}
}

View file

@ -0,0 +1,179 @@
<?php
namespace Friendica\Core\Config\Cache;
use ParagonIE\HiddenString\HiddenString;
/**
* The Friendica config cache for the application
* Initial, all *.config.php files are loaded into this cache with the
* ConfigFileLoader ( @see ConfigFileLoader )
*/
class ConfigCache
{
/**
* @var array
*/
private $config;
/**
* @var bool
*/
private $hidePasswordOutput;
/**
* @param array $config A initial config array
* @param bool $hidePasswordOutput True, if cache variables should take extra care of password values
*/
public function __construct(array $config = [], bool $hidePasswordOutput = true)
{
$this->hidePasswordOutput = $hidePasswordOutput;
$this->load($config);
}
/**
* Tries to load the specified configuration array into the config array.
* Doesn't overwrite previously set values by default to prevent default config files to supersede DB Config.
*
* @param array $config
* @param bool $overwrite Force value overwrite if the config key already exists
*/
public function load(array $config, bool $overwrite = false)
{
$categories = array_keys($config);
foreach ($categories as $category) {
if (is_array($config[$category])) {
$keys = array_keys($config[$category]);
foreach ($keys as $key) {
$value = $config[$category][$key];
if (isset($value)) {
if ($overwrite) {
$this->set($category, $key, $value);
} else {
$this->setDefault($category, $key, $value);
}
}
}
}
}
}
/**
* Gets a value from the config cache.
*
* @param string $cat Config category
* @param string $key Config key
*
* @return null|mixed Returns the value of the Config entry or null if not set
*/
public function get(string $cat, string $key = null)
{
if (isset($this->config[$cat][$key])) {
return $this->config[$cat][$key];
} elseif (!isset($key) && isset($this->config[$cat])) {
return $this->config[$cat];
} else {
return null;
}
}
/**
* Sets a default value in the config cache. Ignores already existing keys.
*
* @param string $cat Config category
* @param string $key Config key
* @param mixed $value Default value to set
*/
private function setDefault(string $cat, string $key, $value)
{
if (!isset($this->config[$cat][$key])) {
$this->set($cat, $key, $value);
}
}
/**
* Sets a value in the config cache. Accepts raw output from the config table
*
* @param string $cat Config category
* @param string $key Config key
* @param mixed $value Value to set
*
* @return bool True, if the value is set
*/
public function set(string $cat, string $key, $value)
{
if (!isset($this->config[$cat])) {
$this->config[$cat] = [];
}
if ($this->hidePasswordOutput &&
$key == 'password' &&
is_string($value)) {
$this->config[$cat][$key] = new HiddenString((string)$value);
} else {
$this->config[$cat][$key] = $value;
}
return true;
}
/**
* Deletes a value from the config cache.
*
* @param string $cat Config category
* @param string $key Config key
*
* @return bool true, if deleted
*/
public function delete(string $cat, string $key)
{
if (isset($this->config[$cat][$key])) {
unset($this->config[$cat][$key]);
if (count($this->config[$cat]) == 0) {
unset($this->config[$cat]);
}
return true;
} else {
return false;
}
}
/**
* Returns the whole configuration
*
* @return array The configuration
*/
public function getAll()
{
return $this->config;
}
/**
* Returns an array with missing categories/Keys
*
* @param array $config The array to check
*
* @return array
*/
public function keyDiff(array $config)
{
$return = [];
$categories = array_keys($config);
foreach ($categories as $category) {
if (is_array($config[$category])) {
$keys = array_keys($config[$category]);
foreach ($keys as $key) {
if (!isset($this->config[$category][$key])) {
$return[$category][$key] = $config[$category][$key];
}
}
}
}
return $return;
}
}

View file

@ -0,0 +1,189 @@
<?php
namespace Friendica\Core\Config\Cache;
use ParagonIE\HiddenString\HiddenString;
/**
* The Friendica config cache for users
*/
class PConfigCache
{
/**
* @var array
*/
private $config;
/**
* @var bool
*/
private $hidePasswordOutput;
/**
* @param bool $hidePasswordOutput True, if cache variables should take extra care of password values
*/
public function __construct(bool $hidePasswordOutput = true)
{
$this->hidePasswordOutput = $hidePasswordOutput;
}
/**
* Tries to load the specified configuration array into the user specific config array.
* Doesn't overwrite previously set values by default to prevent default config files to supersede DB Config.
*
* @param int $uid
* @param array $config
*/
public function load($uid, array $config)
{
if (!is_int($uid)) {
return;
}
$categories = array_keys($config);
foreach ($categories as $category) {
if (isset($config[$category]) && is_array($config[$category])) {
$keys = array_keys($config[$category]);
foreach ($keys as $key) {
$value = $config[$category][$key];
if (isset($value)) {
$this->set($uid, $category, $key, $value);
}
}
}
}
}
/**
* Retrieves a value from the user config cache
*
* @param int $uid User Id
* @param string $cat Config category
* @param string $key Config key
*
* @return null|string The value of the config entry or null if not set
*/
public function get($uid, string $cat, string $key = null)
{
if (!is_int($uid)) {
return null;
}
if (isset($this->config[$uid][$cat][$key])) {
return $this->config[$uid][$cat][$key];
} elseif (!isset($key) && isset($this->config[$uid][$cat])) {
return $this->config[$uid][$cat];
} else {
return null;
}
}
/**
* Sets a value in the user config cache
*
* Accepts raw output from the pconfig table
*
* @param int $uid User Id
* @param string $cat Config category
* @param string $key Config key
* @param mixed $value Value to set
*
* @return bool Set successful
*/
public function set($uid, string $cat, string $key, $value)
{
if (!is_int($uid)) {
return false;
}
if (!isset($this->config[$uid]) || !is_array($this->config[$uid])) {
$this->config[$uid] = [];
}
if (!isset($this->config[$uid][$cat])) {
$this->config[$uid][$cat] = [];
}
if ($this->hidePasswordOutput &&
$key == 'password' &&
!empty($value) && is_string($value)) {
$this->config[$uid][$cat][$key] = new HiddenString((string)$value);
} else {
$this->config[$uid][$cat][$key] = $value;
}
return true;
}
/**
* Deletes a value from the user config cache
*
* @param int $uid User Id
* @param string $cat Config category
* @param string $key Config key
*
* @return bool true, if deleted
*/
public function delete($uid, string $cat, string $key)
{
if (!is_int($uid)) {
return false;
}
if (isset($this->config[$uid][$cat][$key])) {
unset($this->config[$uid][$cat][$key]);
if (count($this->config[$uid][$cat]) == 0) {
unset($this->config[$uid][$cat]);
if (count($this->config[$uid]) == 0) {
unset($this->config[$uid]);
}
}
return true;
} else {
return false;
}
}
/**
* Returns the whole configuration
*
* @return array The configuration
*/
public function getAll()
{
return $this->config;
}
/**
* Returns an array with missing categories/Keys
*
* @param array $config The array to check
*
* @return array
*/
public function keyDiff(array $config)
{
$return = [];
$categories = array_keys($config);
foreach ($categories as $category) {
if (is_array($config[$category])) {
$keys = array_keys($config[$category]);
foreach ($keys as $key) {
if (!isset($this->config[$category][$key])) {
$return[$category][$key] = $config[$category][$key];
}
}
}
}
return $return;
}
}

View file

@ -0,0 +1,100 @@
<?php
namespace Friendica\Core\Config;
use Friendica\Model;
/**
* This class is responsible for all system-wide configuration values in Friendica
* There are two types of storage
* - The Config-Files (loaded into the FileCache @see Cache\ConfigCache )
* - The Config-DB-Table (per Config-DB-model @see Model\Config\Config )
*/
abstract class Configuration
{
/**
* @var Cache\ConfigCache
*/
protected $configCache;
/**
* @var Model\Config\Config
*/
protected $configModel;
/**
* @param Cache\ConfigCache $configCache The configuration cache (based on the config-files)
* @param Model\Config\Config $configModel The configuration model
*/
public function __construct(Cache\ConfigCache $configCache, Model\Config\Config $configModel)
{
$this->configCache = $configCache;
$this->configModel = $configModel;
}
/**
* Returns the Config Cache
*
* @return Cache\ConfigCache
*/
public function getCache()
{
return $this->configCache;
}
/**
* @brief Loads all configuration values of family into a cached storage.
*
* All configuration values of the system are stored in the cache ( @see ConfigCache )
*
* @param string $cat The category of the configuration value
*
* @return void
*/
abstract public function load(string $cat = 'config');
/**
* @brief Get a particular user's config variable given the category name
* ($cat) and a $key.
*
* Get a particular config value from the given category ($cat)
* and the $key from a cached storage either from the $this->configAdapter
* (@see IConfigAdapter ) or from the $this->configCache (@see ConfigCache ).
*
* @param string $cat The category of the configuration value
* @param string $key The configuration key to query
* @param mixed $default_value optional, The value to return if key is not set (default: null)
* @param boolean $refresh optional, If true the config is loaded from the db and not from the cache (default: false)
*
* @return mixed Stored value or null if it does not exist
*/
abstract public function get(string $cat, string $key, $default_value = null, bool $refresh = false);
/**
* @brief Sets a configuration value for system config
*
* Stores a config value ($value) in the category ($cat) under the key ($key)
*
* Note: Please do not store booleans - convert to 0/1 integer values!
*
* @param string $cat The category of the configuration value
* @param string $key The configuration key to set
* @param mixed $value The value to store
*
* @return bool Operation success
*/
abstract public function set(string $cat, string $key, $value);
/**
* @brief Deletes the given key from the system configuration.
*
* Removes the configured value from the stored cache in $this->configCache
* (@see ConfigCache ) and removes it from the database (@see IConfigAdapter ).
*
* @param string $cat The category of the configuration value
* @param string $key The configuration key to delete
*
* @return bool
*/
abstract public function delete(string $cat, string $key);
}

View file

@ -1,72 +0,0 @@
<?php
namespace Friendica\Core\Config;
/**
*
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
interface IConfigAdapter
{
/**
* @brief Loads all configuration values into a cached storage.
*
* All configuration values of the system are stored in global cache
* which is available under the global variable $a->config
*
* @param string $cat The category of the configuration values to load
*
* @return void
*/
public function load($cat = "config");
/**
* @brief Get a particular user's config variable given the category name
* ($family) and a key.
*
* Get a particular config value from the given category ($family)
* and the $key from a cached storage in $a->config[$uid].
* $instore is only used by the set_config function
* to determine if the key already exists in the DB
* If a key is found in the DB but doesn't exist in
* local config cache, pull it into the cache so we don't have
* to hit the DB again for this item.
*
* @param string $cat The category of the configuration value
* @param string $k The configuration key to query
* @param mixed $default_value optional, The value to return if key is not set (default: null)
* @param boolean $refresh optional, If true the config is loaded from the db and not from the cache (default: false)
*
* @return mixed Stored value or null if it does not exist
*/
public function get($cat, $k, $default_value = null, $refresh = false);
/**
* @brief Sets a configuration value for system config
*
* Stores a config value ($value) in the category ($family) under the key ($key)
* for the user_id $uid.
*
* Note: Please do not store booleans - convert to 0/1 integer values!
*
* @param string $family The category of the configuration value
* @param string $key The configuration key to set
* @param mixed $value The value to store
*
* @return bool Operation success
*/
public function set($cat, $k, $value);
/**
* @brief Deletes the given key from the system configuration.
*
* Removes the configured value from the stored cache in $a->config
* and removes it from the database.
*
* @param string $cat The category of the configuration value
* @param string $k The configuration key to delete
*
* @return mixed
*/
public function delete($cat, $k);
}

View file

@ -1,77 +0,0 @@
<?php
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
namespace Friendica\Core\Config;
/**
*
* @author benlo
*/
interface IPConfigAdapter
{
/**
* @brief Loads all configuration values of a user's config family into a cached storage.
*
* All configuration values of the given user are stored in global cache
* which is available under the global variable $a->config[$uid].
*
* @param string $uid The user_id
* @param string $cat The category of the configuration value
*
* @return void
*/
public function load($uid, $cat);
/**
* @brief Get a particular user's config variable given the category name
* ($family) and a key.
*
* Get a particular user's config value from the given category ($family)
* and the $key from a cached storage in $a->config[$uid].
*
* @param string $uid The user_id
* @param string $cat The category of the configuration value
* @param string $k The configuration key to query
* @param mixed $default_value optional, The value to return if key is not set (default: null)
* @param boolean $refresh optional, If true the config is loaded from the db and not from the cache (default: false)
*
* @return mixed Stored value or null if it does not exist
*/
public function get($uid, $cat, $k, $default_value = null, $refresh = false);
/**
* @brief Sets a configuration value for a user
*
* Stores a config value ($value) in the category ($family) under the key ($key)
* for the user_id $uid.
*
* @note Please do not store booleans - convert to 0/1 integer values!
*
* @param string $uid The user_id
* @param string $cat The category of the configuration value
* @param string $k The configuration key to set
* @param string $value The value to store
*
* @return bool Operation success
*/
public function set($uid, $cat, $k, $value);
/**
* @brief Deletes the given key from the users's configuration.
*
* Removes the configured value from the stored cache in $a->config[$uid]
* and removes it from the database.
*
* @param string $uid The user_id
* @param string $cat The category of the configuration value
* @param string $k The configuration key to delete
*
* @return mixed
*/
public function delete($uid, $cat, $k);
}

View file

@ -1,137 +0,0 @@
<?php
namespace Friendica\Core\Config;
use Friendica\BaseObject;
use Friendica\Database\DBA;
require_once 'include/dba.php';
/**
* JustInTime Configuration Adapter
*
* Default Config Adapter. Provides the best performance for pages loading few configuration variables.
*
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class JITConfigAdapter extends BaseObject implements IConfigAdapter
{
private $cache;
private $in_db;
public function load($cat = "config")
{
// We don't preload "system" anymore.
// This reduces the number of database reads a lot.
if ($cat === 'system') {
return;
}
$configs = DBA::select('config', ['v', 'k'], ['cat' => $cat]);
while ($config = DBA::fetch($configs)) {
$k = $config['k'];
self::getApp()->setConfigValue($cat, $k, $config['v']);
if ($cat !== 'config') {
$this->cache[$cat][$k] = $config['v'];
$this->in_db[$cat][$k] = true;
}
}
DBA::close($configs);
}
public function get($cat, $k, $default_value = null, $refresh = false)
{
$a = self::getApp();
if (!$refresh) {
// Do we have the cached value? Then return it
if (isset($this->cache[$cat][$k])) {
if ($this->cache[$cat][$k] === '!<unset>!') {
return $default_value;
} else {
return $this->cache[$cat][$k];
}
}
}
$config = DBA::selectFirst('config', ['v'], ['cat' => $cat, 'k' => $k]);
if (DBA::isResult($config)) {
// manage array value
$value = (preg_match("|^a:[0-9]+:{.*}$|s", $config['v']) ? unserialize($config['v']) : $config['v']);
// Assign the value from the database to the cache
$this->cache[$cat][$k] = $value;
$this->in_db[$cat][$k] = true;
return $value;
} elseif (isset($a->config[$cat][$k])) {
// Assign the value (mostly) from config/local.ini.php file to the cache
$this->cache[$cat][$k] = $a->config[$cat][$k];
$this->in_db[$cat][$k] = false;
return $a->config[$cat][$k];
} elseif (isset($a->config[$k])) {
// Assign the value (mostly) from config/local.ini.php file to the cache
$this->cache[$k] = $a->config[$k];
$this->in_db[$k] = false;
return $a->config[$k];
}
$this->cache[$cat][$k] = '!<unset>!';
$this->in_db[$cat][$k] = false;
return $default_value;
}
public function set($cat, $k, $value)
{
$a = self::getApp();
// We store our setting values in a string variable.
// So we have to do the conversion here so that the compare below works.
// The exception are array values.
$dbvalue = (!is_array($value) ? (string)$value : $value);
$stored = $this->get($cat, $k, null, true);
if (!isset($this->in_db[$cat])) {
$this->in_db[$cat] = [];
}
if (!isset($this->in_db[$cat][$k])) {
$this->in_db[$cat] = false;
}
if (($stored === $dbvalue) && $this->in_db[$cat][$k]) {
return true;
}
self::getApp()->setConfigValue($cat, $k, $value);
// Assign the just added value to the cache
$this->cache[$cat][$k] = $dbvalue;
// manage array value
$dbvalue = (is_array($value) ? serialize($value) : $dbvalue);
$result = DBA::update('config', ['v' => $dbvalue], ['cat' => $cat, 'k' => $k], true);
if ($result) {
$this->in_db[$cat][$k] = true;
}
return $result;
}
public function delete($cat, $k)
{
if (isset($this->cache[$cat][$k])) {
unset($this->cache[$cat][$k]);
unset($this->in_db[$cat][$k]);
}
$result = DBA::delete('config', ['cat' => $cat, 'k' => $k]);
return $result;
}
}

View file

@ -1,117 +0,0 @@
<?php
namespace Friendica\Core\Config;
use Friendica\BaseObject;
use Friendica\Database\DBA;
require_once 'include/dba.php';
/**
* JustInTime User Configuration Adapter
*
* Default PConfig Adapter. Provides the best performance for pages loading few configuration variables.
*
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class JITPConfigAdapter extends BaseObject implements IPConfigAdapter
{
private $in_db;
public function load($uid, $cat)
{
$a = self::getApp();
$pconfigs = DBA::select('pconfig', ['v', 'k'], ['cat' => $cat, 'uid' => $uid]);
if (DBA::isResult($pconfigs)) {
while ($pconfig = DBA::fetch($pconfigs)) {
$k = $pconfig['k'];
self::getApp()->setPConfigValue($uid, $cat, $k, $pconfig['v']);
$this->in_db[$uid][$cat][$k] = true;
}
} else if ($cat != 'config') {
// Negative caching
$a->config[$uid][$cat] = "!<unset>!";
}
DBA::close($pconfigs);
}
public function get($uid, $cat, $k, $default_value = null, $refresh = false)
{
$a = self::getApp();
if (!$refresh) {
// Looking if the whole family isn't set
if (isset($a->config[$uid][$cat])) {
if ($a->config[$uid][$cat] === '!<unset>!') {
return $default_value;
}
}
if (isset($a->config[$uid][$cat][$k])) {
if ($a->config[$uid][$cat][$k] === '!<unset>!') {
return $default_value;
}
return $a->config[$uid][$cat][$k];
}
}
$pconfig = DBA::selectFirst('pconfig', ['v'], ['uid' => $uid, 'cat' => $cat, 'k' => $k]);
if (DBA::isResult($pconfig)) {
$val = (preg_match("|^a:[0-9]+:{.*}$|s", $pconfig['v']) ? unserialize($pconfig['v']) : $pconfig['v']);
self::getApp()->setPConfigValue($uid, $cat, $k, $val);
$this->in_db[$uid][$cat][$k] = true;
return $val;
} else {
self::getApp()->setPConfigValue($uid, $cat, $k, '!<unset>!');
$this->in_db[$uid][$cat][$k] = false;
return $default_value;
}
}
public function set($uid, $cat, $k, $value)
{
// We store our setting values in a string variable.
// So we have to do the conversion here so that the compare below works.
// The exception are array values.
$dbvalue = (!is_array($value) ? (string)$value : $value);
$stored = $this->get($uid, $cat, $k, null, true);
if (($stored === $dbvalue) && $this->in_db[$uid][$cat][$k]) {
return true;
}
self::getApp()->setPConfigValue($uid, $cat, $k, $value);
// manage array value
$dbvalue = (is_array($value) ? serialize($value) : $dbvalue);
$result = DBA::update('pconfig', ['v' => $dbvalue], ['uid' => $uid, 'cat' => $cat, 'k' => $k], true);
if ($result) {
$this->in_db[$uid][$cat][$k] = true;
}
return $result;
}
public function delete($uid, $cat, $k)
{
self::getApp()->deletePConfigValue($uid, $cat, $k);
if (!empty($this->in_db[$uid][$cat][$k])) {
unset($this->in_db[$uid][$cat][$k]);
}
$result = DBA::delete('pconfig', ['uid' => $uid, 'cat' => $cat, 'k' => $k]);
return $result;
}
}

View file

@ -0,0 +1,121 @@
<?php
namespace Friendica\Core\Config;
use Friendica\Model;
/**
* This class implements the Just-In-Time configuration, which will cache
* config values in a cache, once they are retrieved.
*
* Default Configuration type.
* Provides the best performance for pages loading few configuration variables.
*/
class JitConfiguration extends Configuration
{
/**
* @var array Array of already loaded db values (even if there was no value)
*/
private $db_loaded;
/**
* @param Cache\ConfigCache $configCache The configuration cache (based on the config-files)
* @param Model\Config\Config $configModel The configuration model
*/
public function __construct(Cache\ConfigCache $configCache, Model\Config\Config $configModel)
{
parent::__construct($configCache, $configModel);
$this->db_loaded = [];
$this->load();
}
/**
* {@inheritDoc}
*
*/
public function load(string $cat = 'config')
{
// If not connected, do nothing
if (!$this->configModel->isConnected()) {
return;
}
$config = $this->configModel->load($cat);
if (!empty($config[$cat])) {
foreach ($config[$cat] as $key => $value) {
$this->db_loaded[$cat][$key] = true;
}
}
// load the whole category out of the DB into the cache
$this->configCache->load($config, true);
}
/**
* {@inheritDoc}
*/
public function get(string $cat, string $key, $default_value = null, bool $refresh = false)
{
// if the value isn't loaded or refresh is needed, load it to the cache
if ($this->configModel->isConnected() &&
(empty($this->db_loaded[$cat][$key]) ||
$refresh)) {
$dbvalue = $this->configModel->get($cat, $key);
if (isset($dbvalue)) {
$this->configCache->set($cat, $key, $dbvalue);
unset($dbvalue);
}
$this->db_loaded[$cat][$key] = true;
}
// use the config cache for return
$result = $this->configCache->get($cat, $key);
return (isset($result)) ? $result : $default_value;
}
/**
* {@inheritDoc}
*/
public function set(string $cat, string $key, $value)
{
// set the cache first
$cached = $this->configCache->set($cat, $key, $value);
// If there is no connected adapter, we're finished
if (!$this->configModel->isConnected()) {
return $cached;
}
$stored = $this->configModel->set($cat, $key, $value);
$this->db_loaded[$cat][$key] = $stored;
return $cached && $stored;
}
/**
* {@inheritDoc}
*/
public function delete(string $cat, string $key)
{
$cacheRemoved = $this->configCache->delete($cat, $key);
if (isset($this->db_loaded[$cat][$key])) {
unset($this->db_loaded[$cat][$key]);
}
if (!$this->configModel->isConnected()) {
return $cacheRemoved;
}
$storeRemoved = $this->configModel->delete($cat, $key);
return $cacheRemoved || $storeRemoved;
}
}

View file

@ -0,0 +1,131 @@
<?php
namespace Friendica\Core\Config;
use Friendica\Model;
/**
* This class implements the Just-In-Time configuration, which will cache
* user config values in a cache, once they are retrieved.
*
* Default Configuration type.
* Provides the best performance for pages loading few configuration variables.
*/
class JitPConfiguration extends PConfiguration
{
/**
* @var array Array of already loaded db values (even if there was no value)
*/
private $db_loaded;
/**
* @param Cache\PConfigCache $configCache The configuration cache
* @param Model\Config\PConfig $configModel The configuration model
*/
public function __construct(Cache\PConfigCache $configCache, Model\Config\PConfig $configModel)
{
parent::__construct($configCache, $configModel);
$this->db_loaded = [];
}
/**
* {@inheritDoc}
*
*/
public function load(int $uid, string $cat = 'config')
{
// If not connected or no uid, do nothing
if (!$uid || !$this->configModel->isConnected()) {
return;
}
$config = $this->configModel->load($uid, $cat);
if (!empty($config[$cat])) {
foreach ($config[$cat] as $key => $value) {
$this->db_loaded[$uid][$cat][$key] = true;
}
}
// load the whole category out of the DB into the cache
$this->configCache->load($uid, $config);
}
/**
* {@inheritDoc}
*/
public function get(int $uid, string $cat, string $key, $default_value = null, bool $refresh = false)
{
if (!$uid) {
return $default_value;
}
// if the value isn't loaded or refresh is needed, load it to the cache
if ($this->configModel->isConnected() &&
(empty($this->db_loaded[$uid][$cat][$key]) ||
$refresh)) {
$dbvalue = $this->configModel->get($uid, $cat, $key);
if (isset($dbvalue)) {
$this->configCache->set($uid, $cat, $key, $dbvalue);
unset($dbvalue);
}
$this->db_loaded[$uid][$cat][$key] = true;
}
// use the config cache for return
$result = $this->configCache->get($uid, $cat, $key);
return (isset($result)) ? $result : $default_value;
}
/**
* {@inheritDoc}
*/
public function set(int $uid, string $cat, string $key, $value)
{
if (!$uid) {
return false;
}
// set the cache first
$cached = $this->configCache->set($uid, $cat, $key, $value);
// If there is no connected adapter, we're finished
if (!$this->configModel->isConnected()) {
return $cached;
}
$stored = $this->configModel->set($uid, $cat, $key, $value);
$this->db_loaded[$uid][$cat][$key] = $stored;
return $cached && $stored;
}
/**
* {@inheritDoc}
*/
public function delete(int $uid, string $cat, string $key)
{
if (!$uid) {
return false;
}
$cacheRemoved = $this->configCache->delete($uid, $cat, $key);
if (isset($this->db_loaded[$uid][$cat][$key])) {
unset($this->db_loaded[$uid][$cat][$key]);
}
if (!$this->configModel->isConnected()) {
return $cacheRemoved;
}
$storeRemoved = $this->configModel->delete($uid, $cat, $key);
return $cacheRemoved || $storeRemoved;
}
}

View file

@ -0,0 +1,109 @@
<?php
namespace Friendica\Core\Config;
use Friendica\Model;
/**
* This class is responsible for the user-specific configuration values in Friendica
* The values are set through the Config-DB-Table (per Config-DB-model @see Model\Config\PConfig)
*
* The configuration cache (@see Cache\PConfigCache ) is used for temporary caching of database calls. This will
* increase the performance.
*/
abstract class PConfiguration
{
/**
* @var Cache\PConfigCache
*/
protected $configCache;
/**
* @var Model\Config\PConfig
*/
protected $configModel;
/**
* @param Cache\PConfigCache $configCache The configuration cache
* @param Model\Config\PConfig $configModel The configuration model
*/
public function __construct(Cache\PConfigCache $configCache, Model\Config\PConfig $configModel)
{
$this->configCache = $configCache;
$this->configModel = $configModel;
}
/**
* Returns the Config Cache
*
* @return Cache\PConfigCache
*/
public function getCache()
{
return $this->configCache;
}
/**
* Loads all configuration values of a user's config family into a cached storage.
*
* All configuration values of the given user are stored with the $uid in the cache
*
* @param int $uid The user_id
* @param string $cat The category of the configuration value
*
* @return void
* @see PConfigCache )
*
*/
abstract public function load(int $uid, string $cat = 'config');
/**
* Get a particular user's config variable given the category name
* ($cat) and a key.
*
* Get a particular user's config value from the given category ($cat)
* and the $key with the $uid from a cached storage either from the $this->configAdapter
* (@see IConfigAdapter ) or from the $this->configCache (@see PConfigCache ).
*
* @param int $uid The user_id
* @param string $cat The category of the configuration value
* @param string $key The configuration key to query
* @param mixed $default_value optional, The value to return if key is not set (default: null)
* @param boolean $refresh optional, If true the config is loaded from the db and not from the cache (default: false)
*
* @return mixed Stored value or null if it does not exist
*/
abstract public function get(int $uid, string $cat, string $key, $default_value = null, bool $refresh = false);
/**
* Sets a configuration value for a user
*
* Stores a config value ($value) in the category ($family) under the key ($key)
* for the user_id $uid.
*
* @note Please do not store booleans - convert to 0/1 integer values!
*
* @param int $uid The user_id
* @param string $cat The category of the configuration value
* @param string $key The configuration key to set
* @param mixed $value The value to store
*
* @return bool Operation success
*/
abstract public function set(int $uid, string $cat, string $key, $value);
/**
* Deletes the given key from the users's configuration.
*
* Removes the configured value from the stored cache in $this->configCache
* (@see ConfigCache ) and removes it from the database (@see IConfigAdapter )
* with the given $uid.
*
* @param int $uid The user_id
* @param string $cat The category of the configuration value
* @param string $key The configuration key to delete
*
* @return bool
*/
abstract public function delete(int $uid, string $cat, string $key);
}

View file

@ -1,88 +0,0 @@
<?php
namespace Friendica\Core\Config;
use Exception;
use Friendica\BaseObject;
use Friendica\Database\DBA;
require_once 'include/dba.php';
/**
* Preload Configuration Adapter
*
* Minimizes the number of database queries to retrieve configuration values at the cost of memory.
*
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class PreloadConfigAdapter extends BaseObject implements IConfigAdapter
{
private $config_loaded = false;
public function __construct()
{
$this->load();
}
public function load($family = 'config')
{
if ($this->config_loaded) {
return;
}
$configs = DBA::select('config', ['cat', 'v', 'k']);
while ($config = DBA::fetch($configs)) {
self::getApp()->setConfigValue($config['cat'], $config['k'], $config['v']);
}
DBA::close($configs);
$this->config_loaded = true;
}
public function get($cat, $k, $default_value = null, $refresh = false)
{
if ($refresh) {
$config = DBA::selectFirst('config', ['v'], ['cat' => $cat, 'k' => $k]);
if (DBA::isResult($config)) {
self::getApp()->setConfigValue($cat, $k, $config['v']);
}
}
$return = self::getApp()->getConfigValue($cat, $k, $default_value);
return $return;
}
public function set($cat, $k, $value)
{
// We store our setting values as strings.
// So we have to do the conversion here so that the compare below works.
// The exception are array values.
$compare_value = !is_array($value) ? (string)$value : $value;
if (self::getApp()->getConfigValue($cat, $k) === $compare_value) {
return true;
}
self::getApp()->setConfigValue($cat, $k, $value);
// manage array value
$dbvalue = is_array($value) ? serialize($value) : $value;
$result = DBA::update('config', ['v' => $dbvalue], ['cat' => $cat, 'k' => $k], true);
if (!$result) {
throw new Exception('Unable to store config value in [' . $cat . '][' . $k . ']');
}
return true;
}
public function delete($cat, $k)
{
self::getApp()->deleteConfigValue($cat, $k);
$result = DBA::delete('config', ['cat' => $cat, 'k' => $k]);
return $result;
}
}

View file

@ -0,0 +1,116 @@
<?php
namespace Friendica\Core\Config;
use Friendica\Model;
/**
* This class implements the preload configuration, which will cache
* all config values per call in a cache.
*
* Minimizes the number of database queries to retrieve configuration values at the cost of memory.
*/
class PreloadConfiguration extends Configuration
{
/** @var bool */
private $config_loaded;
/**
* @param Cache\ConfigCache $configCache The configuration cache (based on the config-files)
* @param Model\Config\Config $configModel The configuration model
*/
public function __construct(Cache\ConfigCache $configCache, Model\Config\Config $configModel)
{
parent::__construct($configCache, $configModel);
$this->config_loaded = false;
$this->load();
}
/**
* {@inheritDoc}
*
* This loads all config values everytime load is called
*
*/
public function load(string $cat = 'config')
{
// Don't load the whole configuration twice
if ($this->config_loaded) {
return;
}
// If not connected, do nothing
if (!$this->configModel->isConnected()) {
return;
}
$config = $this->configModel->load();
$this->config_loaded = true;
// load the whole category out of the DB into the cache
$this->configCache->load($config, true);
}
/**
* {@inheritDoc}
*/
public function get(string $cat, string $key, $default_value = null, bool $refresh = false)
{
if ($refresh) {
if ($this->configModel->isConnected()) {
$config = $this->configModel->get($cat, $key);
if (isset($config)) {
$this->configCache->set($cat, $key, $config);
}
}
}
// use the config cache for return
$result = $this->configCache->get($cat, $key);
return (isset($result)) ? $result : $default_value;
}
/**
* {@inheritDoc}
*/
public function set(string $cat, string $key, $value)
{
if (!$this->config_loaded) {
$this->load();
}
// set the cache first
$cached = $this->configCache->set($cat, $key, $value);
// If there is no connected adapter, we're finished
if (!$this->configModel->isConnected()) {
return $cached;
}
$stored = $this->configModel->set($cat, $key, $value);
return $cached && $stored;
}
/**
* {@inheritDoc}
*/
public function delete(string $cat, string $key)
{
if ($this->config_loaded) {
$this->load();
}
$cacheRemoved = $this->configCache->delete($cat, $key);
if (!$this->configModel->isConnected()) {
return $cacheRemoved;
}
$storeRemoved = $this->configModel->delete($cat, $key);
return $cacheRemoved || $storeRemoved;
}
}

View file

@ -1,105 +0,0 @@
<?php
namespace Friendica\Core\Config;
use Exception;
use Friendica\BaseObject;
use Friendica\Database\DBA;
require_once 'include/dba.php';
/**
* Preload User Configuration Adapter
*
* Minimizes the number of database queries to retrieve configuration values at the cost of memory.
*
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class PreloadPConfigAdapter extends BaseObject implements IPConfigAdapter
{
private $config_loaded = false;
public function __construct($uid)
{
$this->load($uid, 'config');
}
public function load($uid, $family)
{
if ($this->config_loaded) {
return;
}
if (empty($uid)) {
return;
}
$pconfigs = DBA::select('pconfig', ['cat', 'v', 'k'], ['uid' => $uid]);
while ($pconfig = DBA::fetch($pconfigs)) {
self::getApp()->setPConfigValue($uid, $pconfig['cat'], $pconfig['k'], $pconfig['v']);
}
DBA::close($pconfigs);
$this->config_loaded = true;
}
public function get($uid, $cat, $k, $default_value = null, $refresh = false)
{
if (!$this->config_loaded) {
$this->load($uid, $cat);
}
if ($refresh) {
$config = DBA::selectFirst('pconfig', ['v'], ['uid' => $uid, 'cat' => $cat, 'k' => $k]);
if (DBA::isResult($config)) {
self::getApp()->setPConfigValue($uid, $cat, $k, $config['v']);
} else {
self::getApp()->deletePConfigValue($uid, $cat, $k);
}
}
$return = self::getApp()->getPConfigValue($uid, $cat, $k, $default_value);
return $return;
}
public function set($uid, $cat, $k, $value)
{
if (!$this->config_loaded) {
$this->load($uid, $cat);
}
// We store our setting values as strings.
// So we have to do the conversion here so that the compare below works.
// The exception are array values.
$compare_value = !is_array($value) ? (string)$value : $value;
if (self::getApp()->getPConfigValue($uid, $cat, $k) === $compare_value) {
return true;
}
self::getApp()->setPConfigValue($uid, $cat, $k, $value);
// manage array value
$dbvalue = is_array($value) ? serialize($value) : $value;
$result = DBA::update('pconfig', ['v' => $dbvalue], ['uid' => $uid, 'cat' => $cat, 'k' => $k], true);
if (!$result) {
throw new Exception('Unable to store config value in [' . $uid . '][' . $cat . '][' . $k . ']');
}
return true;
}
public function delete($uid, $cat, $k)
{
if (!$this->config_loaded) {
$this->load($uid, $cat);
}
self::getApp()->deletePConfigValue($uid, $cat, $k);
$result = DBA::delete('pconfig', ['uid' => $uid, 'cat' => $cat, 'k' => $k]);
return $result;
}
}

View file

@ -0,0 +1,128 @@
<?php
namespace Friendica\Core\Config;
use Friendica\Model;
/**
* This class implements the preload configuration, which will cache
* all user config values per call in a cache.
*
* Minimizes the number of database queries to retrieve configuration values at the cost of memory.
*/
class PreloadPConfiguration extends PConfiguration
{
/** @var array */
private $config_loaded;
/**
* @param Cache\PConfigCache $configCache The configuration cache
* @param Model\Config\PConfig $configModel The configuration model
*/
public function __construct(Cache\PConfigCache $configCache, Model\Config\PConfig $configModel)
{
parent::__construct($configCache, $configModel);
$this->config_loaded = [];
}
/**
* {@inheritDoc}
*
* This loads all config values everytime load is called
*
*/
public function load(int $uid, string $cat = 'config')
{
// Don't load the whole configuration twice or with invalid uid
if (!$uid || !empty($this->config_loaded[$uid])) {
return;
}
// If not connected, do nothing
if (!$this->configModel->isConnected()) {
return;
}
$config = $this->configModel->load($uid);
$this->config_loaded[$uid] = true;
// load the whole category out of the DB into the cache
$this->configCache->load($uid, $config);
}
/**
* {@inheritDoc}
*/
public function get(int $uid, string $cat, string $key, $default_value = null, bool $refresh = false)
{
if (!$uid) {
return $default_value;
}
if (empty($this->config_loaded[$uid])) {
$this->load($uid);
} elseif ($refresh) {
if ($this->configModel->isConnected()) {
$config = $this->configModel->get($uid, $cat, $key);
if (isset($config)) {
$this->configCache->set($uid, $cat, $key, $config);
}
}
}
// use the config cache for return
$result = $this->configCache->get($uid, $cat, $key);
return (isset($result)) ? $result : $default_value;
}
/**
* {@inheritDoc}
*/
public function set(int $uid, string $cat, string $key, $value)
{
if (!$uid) {
return false;
}
if (empty($this->config_loaded[$uid])) {
$this->load($uid);
}
// set the cache first
$cached = $this->configCache->set($uid, $cat, $key, $value);
// If there is no connected adapter, we're finished
if (!$this->configModel->isConnected()) {
return $cached;
}
$stored = $this->configModel->set($uid, $cat, $key, $value);
return $cached && $stored;
}
/**
* {@inheritDoc}
*/
public function delete(int $uid, string $cat, string $key)
{
if (!$uid) {
return false;
}
if (empty($this->config_loaded[$uid])) {
$this->load($uid);
}
$cacheRemoved = $this->configCache->delete($uid, $cat, $key);
if (!$this->configModel->isConnected()) {
return $cacheRemoved;
}
$storeRemoved = $this->configModel->delete($uid, $cat, $key);
return $cacheRemoved || $storeRemoved;
}
}

View file

@ -2,6 +2,9 @@
namespace Friendica\Core;
use Dice\Dice;
use Friendica;
/**
* Description of Console
*
@ -13,24 +16,10 @@ class Console extends \Asika\SimpleConsole\Console
protected $helpOptions = [];
protected $customHelpOptions = ['h', 'help', '?'];
protected $subConsoles = [
'cache' => __NAMESPACE__ . '\Console\Cache',
'config' => __NAMESPACE__ . '\Console\Config',
'createdoxygen' => __NAMESPACE__ . '\Console\CreateDoxygen',
'docbloxerrorchecker' => __NAMESPACE__ . '\Console\DocBloxErrorChecker',
'dbstructure' => __NAMESPACE__ . '\Console\DatabaseStructure',
'extract' => __NAMESPACE__ . '\Console\Extract',
'globalcommunityblock' => __NAMESPACE__ . '\Console\GlobalCommunityBlock',
'globalcommunitysilence' => __NAMESPACE__ . '\Console\GlobalCommunitySilence',
'archivecontact' => __NAMESPACE__ . '\Console\ArchiveContact',
'autoinstall' => __NAMESPACE__ . '\Console\AutomaticInstallation',
'maintenance' => __NAMESPACE__ . '\Console\Maintenance',
'newpassword' => __NAMESPACE__ . '\Console\NewPassword',
'php2po' => __NAMESPACE__ . '\Console\PhpToPo',
'po2php' => __NAMESPACE__ . '\Console\PoToPhp',
'typo' => __NAMESPACE__ . '\Console\Typo',
'postupdate' => __NAMESPACE__ . '\Console\PostUpdate',
];
/**
* @var Dice The DI library
*/
protected $dice;
protected function getHelp()
{
@ -49,12 +38,15 @@ Commands:
archivecontact Archive a contact when you know that it isn't existing anymore
help Show help about a command, e.g (bin/console help config)
autoinstall Starts automatic installation of friendica based on values from htconfig.php
lock Edit site locks
maintenance Set maintenance mode for this node
newpassword Set a new password for a given user
php2po Generate a messages.po file from a strings.php file
po2php Generate a strings.php file from a messages.po file
typo Checks for parse errors in Friendica files
postupdate Execute pending post update scripts (can last days)
serverblock Manage blocked servers
storage Manage storage backend
Options:
-h|--help|-? Show help information
@ -63,6 +55,41 @@ HELP;
return $help;
}
protected $subConsoles = [
'cache' => Friendica\Console\Cache::class,
'config' => Friendica\Console\Config::class,
'createdoxygen' => Friendica\Console\CreateDoxygen::class,
'docbloxerrorchecker' => Friendica\Console\DocBloxErrorChecker::class,
'dbstructure' => Friendica\Console\DatabaseStructure::class,
'extract' => Friendica\Console\Extract::class,
'globalcommunityblock' => Friendica\Console\GlobalCommunityBlock::class,
'globalcommunitysilence' => Friendica\Console\GlobalCommunitySilence::class,
'archivecontact' => Friendica\Console\ArchiveContact::class,
'autoinstall' => Friendica\Console\AutomaticInstallation::class,
'lock' => Friendica\Console\Lock::class,
'maintenance' => Friendica\Console\Maintenance::class,
'newpassword' => Friendica\Console\NewPassword::class,
'php2po' => Friendica\Console\PhpToPo::class,
'po2php' => Friendica\Console\PoToPhp::class,
'typo' => Friendica\Console\Typo::class,
'postupdate' => Friendica\Console\PostUpdate::class,
'serverblock' => Friendica\Console\ServerBlock::class,
'storage' => Friendica\Console\Storage::class,
];
/**
* CliInput Friendica constructor.
*
* @param Dice $dice The DI library
* @param array $argv
*/
public function __construct(Dice $dice, array $argv = null)
{
parent::__construct($argv);
$this->dice = $dice;
}
protected function doExecute()
{
if ($this->getOption('v')) {
@ -71,7 +98,6 @@ HELP;
$this->out('Options: ' . var_export($this->options, true));
}
$showHelp = false;
$subHelp = false;
$command = null;
@ -81,7 +107,6 @@ HELP;
return 0;
} elseif ((count($this->options) === 0 || $this->getOption($this->customHelpOptions) === true || $this->getOption($this->customHelpOptions) === 1) && count($this->args) === 0
) {
$showHelp = true;
} elseif (count($this->args) >= 2 && $this->getArgument(0) == 'help') {
$command = $this->getArgument(1);
$subHelp = true;
@ -121,7 +146,10 @@ HELP;
$className = $this->subConsoles[$command];
$subconsole = new $className($subargs);
Friendica\BaseObject::setDependencyInjection($this->dice);
/** @var Console $subconsole */
$subconsole = $this->dice->create($className, [$subargs]);
foreach ($this->options as $name => $value) {
$subconsole->setOption($name, $value);

View file

@ -1,251 +0,0 @@
<?php
namespace Friendica\Core\Console;
use Asika\SimpleConsole\Console;
use Friendica\BaseObject;
use Friendica\Core\Config;
use Friendica\Core\Install;
use Friendica\Core\Theme;
use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use RuntimeException;
require_once 'include/dba.php';
class AutomaticInstallation extends Console
{
protected function getHelp()
{
return <<<HELP
Installation - Install Friendica automatically
Synopsis
bin/console autoinstall [-h|--help|-?] [-v] [-a] [-f]
Description
Installs Friendica with data based on the local.ini.php file or environment variables
Notes
Not checking .htaccess/URL-Rewrite during CLI installation.
Options
-h|--help|-? Show help information
-v Show more debug information.
-a All setup checks are required (except .htaccess)
-f|--file <config> prepared config file (e.g. "config/local.ini.php" itself) which will override every other config option - except the environment variables)
-s|--savedb Save the DB credentials to the file (if environment variables is used)
-H|--dbhost <host> The host of the mysql/mariadb database (env MYSQL_HOST)
-p|--dbport <port> The port of the mysql/mariadb database (env MYSQL_PORT)
-d|--dbdata <database> The name of the mysql/mariadb database (env MYSQL_DATABASE)
-U|--dbuser <username> The username of the mysql/mariadb database login (env MYSQL_USER or MYSQL_USERNAME)
-P|--dbpass <password> The password of the mysql/mariadb database login (env MYSQL_PASSWORD)
-u|--urlpath <url_path> The URL path of Friendica - f.e. '/friendica' (env FRIENDICA_URL_PATH)
-b|--phppath <php_path> The path of the PHP binary (env FRIENDICA_PHP_PATH)
-A|--admin <mail> The admin email address of Friendica (env FRIENDICA_ADMIN_MAIL)
-T|--tz <timezone> The timezone of Friendica (env FRIENDICA_TZ)
-L|--lang <language> The language of Friendica (env FRIENDICA_LANG)
Environment variables
MYSQL_HOST The host of the mysql/mariadb database (mandatory if mysql and environment is used)
MYSQL_PORT The port of the mysql/mariadb database
MYSQL_USERNAME|MYSQL_USER The username of the mysql/mariadb database login (MYSQL_USERNAME is for mysql, MYSQL_USER for mariadb)
MYSQL_PASSWORD The password of the mysql/mariadb database login
MYSQL_DATABASE The name of the mysql/mariadb database
FRIENDICA_URL_PATH The URL path of Friendica (f.e. '/friendica')
FRIENDICA_PHP_PATH The path of the PHP binary
FRIENDICA_ADMIN_MAIL The admin email address of Friendica (this email will be used for admin access)
FRIENDICA_TZ The timezone of Friendica
FRIENDICA_LANG The langauge of Friendica
Examples
bin/console autoinstall -f 'input.ini.php
Installs Friendica with the prepared 'input.ini.php' file
bin/console autoinstall --savedb
Installs Friendica with environment variables and saves them to the 'config/local.ini.php' file
bin/console autoinstall -h localhost -p 3365 -U user -P passwort1234 -d friendica
Installs Friendica with a local mysql database with credentials
HELP;
}
protected function doExecute()
{
// Initialise the app
$this->out("Initializing setup...\n");
$a = BaseObject::getApp();
$install = new Install();
// if a config file is set,
$config_file = $this->getOption(['f', 'file']);
if (!empty($config_file)) {
if ($config_file != 'config' . DIRECTORY_SEPARATOR . 'local.ini.php') {
// Copy config file
$this->out("Copying config file...\n");
if (!copy($a->getBasePath() . DIRECTORY_SEPARATOR . $config_file, $a->getBasePath() . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'local.ini.php')) {
throw new RuntimeException("ERROR: Saving config file failed. Please copy '$config_file' to '" . $a->getBasePath() . "'" . DIRECTORY_SEPARATOR . "config" . DIRECTORY_SEPARATOR . "local.ini.php' manually.\n");
}
}
$db_host = $a->getConfigValue('database', 'hostname');
$db_user = $a->getConfigValue('database', 'username');
$db_pass = $a->getConfigValue('database', 'password');
$db_data = $a->getConfigValue('database', 'database');
} else {
// Creating config file
$this->out("Creating config file...\n");
$save_db = $this->getOption(['s', 'savedb'], false);
$db_host = $this->getOption(['H', 'dbhost'], ($save_db) ? getenv('MYSQL_HOST') : '');
$db_port = $this->getOption(['p', 'dbport'], ($save_db) ? getenv('MYSQL_PORT') : null);
$db_data = $this->getOption(['d', 'dbdata'], ($save_db) ? getenv('MYSQL_DATABASE') : '');
$db_user = $this->getOption(['U', 'dbuser'], ($save_db) ? getenv('MYSQL_USER') . getenv('MYSQL_USERNAME') : '');
$db_pass = $this->getOption(['P', 'dbpass'], ($save_db) ? getenv('MYSQL_PASSWORD') : '');
$url_path = $this->getOption(['u', 'urlpath'], (!empty('FRIENDICA_URL_PATH')) ? getenv('FRIENDICA_URL_PATH') : null);
$php_path = $this->getOption(['b', 'phppath'], (!empty('FRIENDICA_PHP_PATH')) ? getenv('FRIENDICA_PHP_PATH') : null);
$admin_mail = $this->getOption(['A', 'admin'], (!empty('FRIENDICA_ADMIN_MAIL')) ? getenv('FRIENDICA_ADMIN_MAIL') : '');
$tz = $this->getOption(['T', 'tz'], (!empty('FRIENDICA_TZ')) ? getenv('FRIENDICA_TZ') : '');
$lang = $this->getOption(['L', 'lang'], (!empty('FRIENDICA_LANG')) ? getenv('FRIENDICA_LANG') : '');
$install->createConfig(
$php_path,
$url_path,
((!empty($db_port)) ? $db_host . ':' . $db_port : $db_host),
$db_user,
$db_pass,
$db_data,
$tz,
$lang,
$admin_mail,
$a->getBasePath()
);
}
$this->out(" Complete!\n\n");
// Check basic setup
$this->out("Checking basic setup...\n");
$checkResults = [];
$this->runBasicChecks($install);
$checkResults['basic'] = $install->getChecks();
$errorMessage = $this->extractErrors($checkResults['basic']);
if ($errorMessage !== '') {
throw new RuntimeException($errorMessage);
}
$this->out(" Complete!\n\n");
// Check database connection
$this->out("Checking database...\n");
$checkResults['db'] = array();
$checkResults['db'][] = $this->runDatabaseCheck($db_host, $db_user, $db_pass, $db_data);
$errorMessage = $this->extractErrors($checkResults['db']);
if ($errorMessage !== '') {
throw new RuntimeException($errorMessage);
}
$this->out(" Complete!\n\n");
// Install database
$this->out("Inserting data into database...\n");
$checkResults['data'] = DBStructure::update(false, true, true);
if ($checkResults['data'] !== '') {
throw new RuntimeException("ERROR: DB Database creation error. Is the DB empty?\n");
}
$this->out(" Complete!\n\n");
// Install theme
$this->out("Installing theme\n");
if (!empty(Config::get('system', 'theme'))) {
Theme::install(Config::get('system', 'theme'));
$this->out(" Complete\n\n");
} else {
$this->out(" Theme setting is empty. Please check the file 'config/local.ini.php'\n\n");
}
$this->out("\nInstallation is finished\n");
return 0;
}
/**
* @param Install $install the Installer instance
*/
private function runBasicChecks(Install $install)
{
$install->resetChecks();
$install->checkFunctions();
$install->checkImagick();
$install->checkLocalIni();
$install->checkSmarty3();
$install->checkKeys();
if (!empty(Config::get('config', 'php_path'))) {
if (!$install->checkPHP(Config::get('config', 'php_path'), true)) {
throw new RuntimeException(" ERROR: The php_path is not valid in the config.\n");
}
} else {
throw new RuntimeException(" ERROR: The php_path is not set in the config.\n");
}
$this->out(" NOTICE: Not checking .htaccess/URL-Rewrite during CLI installation.\n");
}
/**
* @param $db_host
* @param $db_user
* @param $db_pass
* @param $db_data
*
* @return array
*/
private function runDatabaseCheck($db_host, $db_user, $db_pass, $db_data)
{
$result = array(
'title' => 'MySQL Connection',
'required' => true,
'status' => true,
'help' => '',
);
if (!DBA::connect($db_host, $db_user, $db_pass, $db_data)) {
$result['status'] = false;
$result['help'] = 'Failed, please check your MySQL settings and credentials.';
}
return $result;
}
/**
* @param array $results
* @return string
*/
private function extractErrors($results)
{
$errorMessage = '';
$allChecksRequired = $this->getOption('a') !== null;
foreach ($results as $result) {
if (($allChecksRequired || $result['required'] === true) && $result['status'] === false) {
$errorMessage .= "--------\n";
$errorMessage .= $result['title'] . ': ' . $result['help'] . "\n";
}
}
return $errorMessage;
}
}

View file

@ -1,67 +0,0 @@
<?php
namespace Friendica\Core\Console;
use Friendica\Core\L10n;
use Friendica\Core\Config;
/**
* Performs database post updates
*
* License: AGPLv3 or later, same as Friendica
*
* @author Tobias Diekershoff <tobias.diekershoff@gmx.net>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class PostUpdate extends \Asika\SimpleConsole\Console
{
protected $helpOptions = ['h', 'help', '?'];
protected function getHelp()
{
$help = <<<HELP
console postupdate - Performs database post updates
Usage
bin/console postupdate [-h|--help|-?] [--reset <version>]
Options
-h|--help|-? Show help information
--reset <version> Reset the post update version
HELP;
return $help;
}
protected function doExecute()
{
$a = \Friendica\BaseObject::getApp();
if ($this->getOption($this->helpOptions)) {
$this->out($this->getHelp());
return 0;
}
$reset_version = $this->getOption('reset');
if (is_bool($reset_version)) {
$this->out($this->getHelp());
return 0;
} elseif ($reset_version) {
Config::set('system', 'post_update_version', $reset_version);
echo L10n::t('Post update version number has been set to %s.', $reset_version) . "\n";
return 0;
}
if ($a->getMode()->isInstall()) {
throw new \RuntimeException('Database isn\'t ready or populated yet');
}
echo L10n::t('Execute pending post updates.') . "\n";
while (!\Friendica\Database\PostUpdate::update()) {
echo '.';
}
echo "\n" . L10n::t('All pending post updates are done.') . "\n";
return 0;
}
}

View file

@ -7,6 +7,7 @@ namespace Friendica\Core;
use Friendica\App;
use Friendica\BaseObject;
use Friendica\Database\DBA;
use Friendica\Util\Strings;
/**
* Some functions to handle hooks
@ -48,9 +49,9 @@ class Hook extends BaseObject
*
* This function is meant to be called by modules on each page load as it works after loadHooks has been called.
*
* @param type $hook
* @param type $file
* @param type $function
* @param string $hook
* @param string $file
* @param string $function
*/
public static function add($hook, $file, $function)
{
@ -70,6 +71,7 @@ class Hook extends BaseObject
* @param string $function the name of the function that the hook will call
* @param int $priority A priority (defaults to 0)
* @return mixed|bool
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function register($hook, $file, $function, $priority = 0)
{
@ -92,6 +94,7 @@ class Hook extends BaseObject
* @param string $file the name of the file that hooks into
* @param string $function the name of the function that the hook called
* @return boolean
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function unregister($hook, $file, $function)
{
@ -131,11 +134,28 @@ class Hook extends BaseObject
* @param integer $priority of the hook
* @param string $name of the hook to call
* @param mixed $data to transmit to the callback handler
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function fork($priority, $name, $data = null)
{
if (array_key_exists($name, self::$hooks)) {
foreach (self::$hooks[$name] as $hook) {
// Call a hook to check if this hook call needs to be forked
if (array_key_exists('hook_fork', self::$hooks)) {
$hookdata = ['name' => $name, 'data' => $data, 'execute' => true];
foreach (self::$hooks['hook_fork'] as $fork_hook) {
if ($hook[0] != $fork_hook[0]) {
continue;
}
self::callSingle(self::getApp(), 'hook_fork', $fork_hook, $hookdata);
}
if (!$hookdata['execute']) {
continue;
}
}
Worker::add($priority, 'ForkHook', $name, $hook, $data);
}
}
@ -147,8 +167,9 @@ class Hook extends BaseObject
* Use this function when you want to be able to allow a hook to manipulate
* the provided data.
*
* @param string $name of the hook to call
* @param string $name of the hook to call
* @param string|array &$data to transmit to the callback handler
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function callAll($name, &$data = null)
{
@ -162,10 +183,11 @@ class Hook extends BaseObject
/**
* @brief Calls a single hook.
*
* @param App $a
* @param string $name of the hook to call
* @param array $hook Hook data
* @param App $a
* @param string $name of the hook to call
* @param array $hook Hook data
* @param string|array &$data to transmit to the callback handler
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function callSingle(App $a, $name, $hook, &$data = null)
{
@ -194,6 +216,8 @@ class Hook extends BaseObject
*/
public static function isAddonApp($name)
{
$name = Strings::sanitizeFilePathItem($name);
if (array_key_exists('app_menu', self::$hooks)) {
foreach (self::$hooks['app_menu'] as $hook) {
if ($hook[0] == 'addon/' . $name . '/' . $name . '.php') {

View file

@ -6,19 +6,33 @@ namespace Friendica\Core;
use DOMDocument;
use Exception;
use Friendica\Core\Config\Cache\ConfigCache;
use Friendica\Database\Database;
use Friendica\Database\DBStructure;
use Friendica\Object\Image;
use Friendica\Util\Network;
use Friendica\Util\Strings;
/**
* Contains methods for installation purpose of Friendica
*/
class Install
class Installer
{
// Default values for the install page
const DEFAULT_LANG = 'en';
const DEFAULT_TZ = 'America/Los_Angeles';
const DEFAULT_HOST = 'localhost';
/**
* @var array the check outcomes
*/
private $checks;
/**
* @var string The path to the PHP binary
*/
private $phppath = null;
/**
* Returns all checks made
*
@ -29,6 +43,23 @@ class Install
return $this->checks;
}
/**
* Returns the PHP path
*
* @return string the PHP Path
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public function getPHPPath()
{
// if not set, determine the PHP path
if (!isset($this->phppath)) {
$this->checkPHP();
$this->resetChecks();
}
return $this->phppath;
}
/**
* Resets all checks
*/
@ -49,13 +80,13 @@ class Install
/**
* Checks the current installation environment. There are optional and mandatory checks.
*
* @param string $basepath The basepath of Friendica
* @param string $baseurl The baseurl of Friendica
* @param string $phpath Optional path to the PHP binary
* @param string $baseurl The baseurl of Friendica
* @param string $phpath Optional path to the PHP binary
*
* @return bool if the check succeed
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public function checkAll($basepath, $baseurl, $phpath = null)
public function checkEnvironment($baseurl, $phpath = null)
{
$returnVal = true;
@ -85,7 +116,7 @@ class Install
$returnVal = false;
}
if (!$this->checkHtAccess($basepath, $baseurl)) {
if (!$this->checkHtAccess($baseurl)) {
$returnVal = false;
}
@ -94,44 +125,68 @@ class Install
/**
* Executes the installation of Friendica in the given environment.
* - Creates `config/local.ini.php`
* - Creates `config/local.config.php`
* - Installs Database Structure
*
* @param string $phppath Path to the PHP-Binary (optional, if not set e.g. 'php' or '/usr/bin/php')
* @param string $urlpath Path based on the URL of Friendica (e.g. '/friendica')
* @param string $dbhost Hostname/IP of the Friendica Database
* @param string $dbuser Username of the Database connection credentials
* @param string $dbpass Password of the Database connection credentials
* @param string $dbdata Name of the Database
* @param string $timezone Timezone of the Friendica Installaton (e.g. 'Europe/Berlin')
* @param string $language 2-letter ISO 639-1 code (eg. 'en')
* @param string $adminmail Mail-Adress of the administrator
* @param string $basepath The basepath of Friendica
* @param ConfigCache $configCache The config cache with all config relevant information
*
* @return bool|string true if the config was created, the text if something went wrong
* @return bool true if the config was created, otherwise false
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public function createConfig($phppath, $urlpath, $dbhost, $dbuser, $dbpass, $dbdata, $timezone, $language, $adminmail, $basepath)
public function createConfig(ConfigCache $configCache)
{
$tpl = get_markup_template('local.ini.tpl');
$txt = replace_macros($tpl,[
'$phpath' => $phppath,
'$dbhost' => $dbhost,
'$dbuser' => $dbuser,
'$dbpass' => $dbpass,
'$dbdata' => $dbdata,
'$timezone' => $timezone,
'$language' => $language,
'$urlpath' => $urlpath,
'$adminmail' => $adminmail,
$basepath = $configCache->get('system', 'basepath');
$tpl = Renderer::getMarkupTemplate('local.config.tpl');
$txt = Renderer::replaceMacros($tpl, [
'$dbhost' => $configCache->get('database', 'hostname'),
'$dbuser' => $configCache->get('database', 'username'),
'$dbpass' => $configCache->get('database', 'password'),
'$dbdata' => $configCache->get('database', 'database'),
'$phpath' => $configCache->get('config', 'php_path'),
'$adminmail' => $configCache->get('config', 'admin_email'),
'$hostname' => $configCache->get('config', 'hostname'),
'$urlpath' => $configCache->get('system', 'urlpath'),
'$baseurl' => $configCache->get('system', 'url'),
'$sslpolicy' => $configCache->get('system', 'ssl_policy'),
'$basepath' => $basepath,
'$timezone' => $configCache->get('system', 'default_timezone'),
'$language' => $configCache->get('system', 'language'),
]);
$result = file_put_contents($basepath . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'local.ini.php', $txt);
$result = file_put_contents($basepath . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'local.config.php', $txt);
if (!$result) {
return $txt;
} else {
return true;
$this->addCheck(L10n::t('The database configuration file "config/local.config.php" could not be written. Please use the enclosed text to create a configuration file in your web server root.'), false, false, htmlentities($txt, ENT_COMPAT, 'UTF-8'));
}
return $result;
}
/***
* Installs the DB-Scheme for Friendica
*
* @param string $basePath The base path of this application
*
* @return bool true if the installation was successful, otherwise false
* @throws Exception
*/
public function installDatabase($basePath)
{
$result = DBStructure::update($basePath, false, true, true);
if ($result) {
$txt = L10n::t('You may need to import the file "database.sql" manually using phpmyadmin or mysql.') . EOL;
$txt .= L10n::t('Please see the file "INSTALL.txt".');
$this->addCheck($txt, false, true, htmlentities($result, ENT_COMPAT, 'UTF-8'));
return false;
}
return true;
}
/**
@ -162,19 +217,24 @@ class Install
* - Checks if a PHP binary is available
* - Checks if it is the CLI version
* - Checks if "register_argc_argv" is enabled
*
* @param string $phppath Optional. The Path to the PHP-Binary
*
* @param string $phppath Optional. The Path to the PHP-Binary
* @param bool $required Optional. If set to true, the PHP-Binary has to exist (Default false)
*
* @return bool false if something required failed
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public function checkPHP($phppath = null, $required = false)
{
$passed = $passed2 = $passed3 = false;
if (isset($phppath)) {
$passed = file_exists($phppath);
} else {
$phppath = trim(shell_exec('which php'));
$passed3 = false;
if (!isset($phppath)) {
$phppath = 'php';
}
$passed = file_exists($phppath);
if (!$passed) {
$phppath = trim(shell_exec('which ' . $phppath));
$passed = strlen($phppath);
}
@ -183,9 +243,10 @@ class Install
$help .= L10n::t('Could not find a command line version of PHP in the web server PATH.') . EOL;
$help .= L10n::t("If you don't have a command line version of PHP installed on your server, you will not be able to run the background processing. See <a href='https://github.com/friendica/friendica/blob/master/doc/Install.md#set-up-the-worker'>'Setup the worker'</a>") . EOL;
$help .= EOL . EOL;
$tpl = get_markup_template('field_input.tpl');
$help .= replace_macros($tpl, [
'$field' => ['phpath', L10n::t('PHP executable path'), $phppath, L10n::t('Enter full path to php executable. You can leave this blank to continue the installation.')],
$tpl = Renderer::getMarkupTemplate('field_input.tpl');
/// @todo Separate backend Installer class and presentation layer/view
$help .= Renderer::replaceMacros($tpl, [
'$field' => ['config-php_path', L10n::t('PHP executable path'), $phppath, L10n::t('Enter full path to php executable. You can leave this blank to continue the installation.')],
]);
$phppath = "";
}
@ -205,12 +266,12 @@ class Install
$this->addCheck(L10n::t('PHP cli binary'), $passed2, true, $help);
} else {
// return if it was required
return $required;
return !$required;
}
if ($passed2) {
$str = autoname(8);
$cmd = "$phppath testargs.php $str";
$str = Strings::getRandomName(8);
$cmd = "$phppath bin/testargs.php $str";
$result = trim(shell_exec($cmd));
$passed3 = $result == $str;
$help = "";
@ -296,6 +357,7 @@ class Install
* - mb_string
* - XML
* - iconv
* - fileinfo
* - POSIX
*
* @return bool false if something required failed
@ -334,7 +396,7 @@ class Install
$help = '';
$status = true;
try {
$xml = new DOMDocument();
new DOMDocument();
} catch (Exception $e) {
$help = L10n::t('Error, XML PHP module required but not installed.');
$status = false;
@ -384,13 +446,27 @@ class Install
);
$returnVal = $returnVal ? $status : false;
$status = $this->checkFunction('json_encode',
L10n::t('JSON PHP module'),
L10n::t('Error: JSON PHP module required but not installed.'),
true
);
$returnVal = $returnVal ? $status : false;
$status = $this->checkFunction('finfo_open',
L10n::t('File Information PHP module'),
L10n::t('Error: File Information PHP module required but not installed.'),
true
);
$returnVal = $returnVal ? $status : false;
return $returnVal;
}
/**
* "config/local.ini.php" - Check
* "config/local.config.php" - Check
*
* Checks if it's possible to create the "config/local.ini.php"
* Checks if it's possible to create the "config/local.config.php"
*
* @return bool false if something required failed
*/
@ -398,17 +474,17 @@ class Install
{
$status = true;
$help = "";
if ((file_exists('config/local.ini.php') && !is_writable('config/local.ini.php')) ||
(!file_exists('config/local.ini.php') && !is_writable('.'))) {
if ((file_exists('config/local.config.php') && !is_writable('config/local.config.php')) ||
(!file_exists('config/local.config.php') && !is_writable('.'))) {
$status = false;
$help = L10n::t('The web installer needs to be able to create a file called "local.ini.php" in the "config" folder of your web server and it is unable to do so.') . EOL;
$help = L10n::t('The web installer needs to be able to create a file called "local.config.php" in the "config" folder of your web server and it is unable to do so.') . EOL;
$help .= L10n::t('This is most often a permission setting, as the web server may not be able to write files in your folder - even if you can.') . EOL;
$help .= L10n::t('At the end of this procedure, we will give you a text to save in a file named local.ini.php in your Friendica "config" folder.') . EOL;
$help .= L10n::t('At the end of this procedure, we will give you a text to save in a file named local.config.php in your Friendica "config" folder.') . EOL;
$help .= L10n::t('You can alternatively skip this procedure and perform a manual installation. Please see the file "INSTALL.txt" for instructions.') . EOL;
}
$this->addCheck(L10n::t('config/local.ini.php is writable'), $status, false, $help);
$this->addCheck(L10n::t('config/local.config.php is writable'), $status, false, $help);
// Local INI File is not required
return true;
@ -444,26 +520,26 @@ class Install
*
* Checks, if "url_rewrite" is enabled in the ".htaccess" file
*
* @param string $basepath The basepath of the app
* @param string $baseurl The baseurl of the app
* @param string $baseurl The baseurl of the app
* @return bool false if something required failed
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public function checkHtAccess($basepath, $baseurl)
public function checkHtAccess($baseurl)
{
$status = true;
$help = "";
$error_msg = "";
if (function_exists('curl_init')) {
$fetchResult = Network::fetchUrlFull($basepath . "/install/testrewrite");
$fetchResult = Network::fetchUrlFull($baseurl . "/install/testrewrite");
$url = normalise_link($baseurl . "/install/testrewrite");
if ($fetchResult->getBody() != "ok") {
$url = Strings::normaliseLink($baseurl . "/install/testrewrite");
if ($fetchResult->getReturnCode() != 204) {
$fetchResult = Network::fetchUrlFull($url);
}
if ($fetchResult->getBody() != "ok") {
if ($fetchResult->getReturnCode() != 204) {
$status = false;
$help = L10n::t('Url rewrite in .htaccess is not working. Check your server configuration.');
$help = L10n::t('Url rewrite in .htaccess is not working. Make sure you copied .htaccess-dist to .htaccess.');
$error_msg = [];
$error_msg['head'] = L10n::t('Error message from Curl when fetching');
$error_msg['url'] = $fetchResult->getRedirectUrl();
@ -510,4 +586,45 @@ class Install
// Imagick is not required
return true;
}
/**
* Checking the Database connection and if it is available for the current installation
*
* @param Database $dba
*
* @return bool true if the check was successful, otherwise false
* @throws Exception
*/
public function checkDB(Database $dba)
{
$dba->reconnect();
if ($dba->isConnected()) {
if (DBStructure::existsTable('user')) {
$this->addCheck(L10n::t('Database already in use.'), false, true, '');
return false;
}
} else {
$this->addCheck(L10n::t('Could not connect to database.'), false, true, '');
return false;
}
return true;
}
/**
* Setup the default cache for a new installation
*
* @param ConfigCache $configCache The configuration cache
* @param string $basePath The determined basepath
*
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public function setUpCache(ConfigCache $configCache, $basePath)
{
$configCache->set('config', 'php_path' , $this->getPHPPath());
$configCache->set('system', 'basepath' , $basePath);
}
}

View file

@ -5,11 +5,7 @@
namespace Friendica\Core;
use Friendica\BaseObject;
use Friendica\Database\DBA;
use Friendica\Core\System;
require_once 'boot.php';
require_once 'include/dba.php';
use Friendica\Core\L10n\L10n as L10nClass;
/**
* Provide Language, Translation, and Localization functions to the application
@ -17,42 +13,6 @@ require_once 'include/dba.php';
*/
class L10n extends BaseObject
{
/**
* A string indicating the current language used for translation:
* - Two-letter ISO 639-1 code.
* - Two-letter ISO 639-1 code + dash + Two-letter ISO 3166-1 alpha-2 country code.
* @var string
*/
private static $lang = '';
/**
* A language code saved for later after pushLang() has been called.
*
* @var string
*/
private static $langSave = '';
/**
* An array of translation strings whose key is the neutral english message.
*
* @var array
*/
private static $strings = [];
/**
* An array of translation strings saved for later after pushLang() has been called.
*
* @var array
*/
private static $stringsSave = [];
/**
* Detects the language and sets the translation table
*/
public static function init()
{
$lang = self::detectLanguage();
self::loadTranslationTable($lang);
}
/**
* Returns the current language code
*
@ -60,73 +20,7 @@ class L10n extends BaseObject
*/
public static function getCurrentLang()
{
return self::$lang;
}
/**
* Sets the language session variable
*/
public static function setSessionVariable()
{
if (!empty($_SESSION['authenticated']) && empty($_SESSION['language'])) {
$_SESSION['language'] = self::$lang;
// we haven't loaded user data yet, but we need user language
if (!empty($_SESSION['uid'])) {
$user = DBA::selectFirst('user', ['language'], ['uid' => $_SESSION['uid']]);
if (DBA::isResult($user)) {
$_SESSION['language'] = $user['language'];
}
}
}
}
public static function setLangFromSession()
{
if (!empty($_SESSION['language']) && $_SESSION['language'] !== self::$lang) {
self::loadTranslationTable($_SESSION['language']);
}
}
/**
* @brief Returns the preferred language from the HTTP_ACCEPT_LANGUAGE header
* @return string The two-letter language code
*/
public static function detectLanguage()
{
$lang_list = [];
if (!empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
// break up string into pieces (languages and q factors)
preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $lang_parse);
if (count($lang_parse[1])) {
// go through the list of prefered languages and add a generic language
// for sub-linguas (e.g. de-ch will add de) if not already in array
for ($i = 0; $i < count($lang_parse[1]); $i++) {
$lang_list[] = strtolower($lang_parse[1][$i]);
if (strlen($lang_parse[1][$i])>3) {
$dashpos = strpos($lang_parse[1][$i], '-');
if (!in_array(substr($lang_parse[1][$i], 0, $dashpos), $lang_list)) {
$lang_list[] = strtolower(substr($lang_parse[1][$i], 0, $dashpos));
}
}
}
}
}
// check if we have translations for the preferred languages and pick the 1st that has
foreach ($lang_list as $lang) {
if ($lang === 'en' || (file_exists("view/lang/$lang") && is_dir("view/lang/$lang"))) {
$preferred = $lang;
break;
}
}
if (isset($preferred)) {
return $preferred;
}
// in case none matches, get the system wide configured language, or fall back to English
return Config::get('system', 'language', 'en');
return self::getClass(L10nClass::class)->getCurrentLang();
}
/**
@ -137,26 +31,15 @@ class L10n extends BaseObject
*
* If called repeatedly, it won't save the translation strings again, just load the new ones.
*
* @see popLang()
* @brief Stores the current language strings and load a different language.
* @param string $lang Language code
*
* @throws \Exception
* @see popLang()
* @brief Stores the current language strings and load a different language.
*/
public static function pushLang($lang)
{
if (!self::$lang) {
self::init();
}
if ($lang === self::$lang) {
return;
}
if (!self::$langSave) {
self::$langSave = self::$lang;
self::$stringsSave = self::$strings;
}
self::loadTranslationTable($lang);
self::getClass(L10nClass::class)->pushLang($lang);
}
/**
@ -164,52 +47,7 @@ class L10n extends BaseObject
*/
public static function popLang()
{
if (!self::$langSave) {
return;
}
self::$strings = self::$stringsSave;
self::$lang = self::$langSave;
self::$stringsSave = [];
self::$langSave = '';
}
/**
* Loads string translation table
*
* First addon strings are loaded, then globals
*
* Uses an App object shim since all the strings files refer to $a->strings
*
* @param string $lang language code to load
*/
private static function loadTranslationTable($lang)
{
if ($lang === self::$lang) {
return;
}
$a = new \stdClass();
$a->strings = [];
// load enabled addons strings
$addons = DBA::select('addon', ['name'], ['installed' => true]);
while ($p = DBA::fetch($addons)) {
$name = $p['name'];
if (file_exists("addon/$name/lang/$lang/strings.php")) {
include "addon/$name/lang/$lang/strings.php";
}
}
if (file_exists("view/lang/$lang/strings.php")) {
include "view/lang/$lang/strings.php";
}
self::$lang = $lang;
self::$strings = $a->strings;
unset($a);
self::getClass(L10nClass::class)->popLang();
}
/**
@ -226,28 +64,12 @@ class L10n extends BaseObject
*
* @param string $s
* @param array $vars Variables to interpolate in the translation string
*
* @return string
*/
public static function t($s, ...$vars)
{
if (empty($s)) {
return '';
}
if (!self::$lang) {
self::init();
}
if (!empty(self::$strings[$s])) {
$t = self::$strings[$s];
$s = is_array($t) ? $t[0] : $t;
}
if (count($vars) > 0) {
$s = sprintf($s, ...$vars);
}
return $s;
return self::getClass(L10nClass::class)->t($s, ...$vars);
}
/**
@ -265,55 +87,14 @@ class L10n extends BaseObject
*
* @param string $singular
* @param string $plural
* @param int $count
* @param int $count
*
* @return string
* @throws \Exception
*/
public static function tt($singular, $plural, $count)
public static function tt(string $singular, string $plural, int $count)
{
if (!is_numeric($count)) {
logger('Non numeric count called by ' . System::callstack(20));
}
if (!self::$lang) {
self::init();
}
if (!empty(self::$strings[$singular])) {
$t = self::$strings[$singular];
if (is_array($t)) {
$plural_function = 'string_plural_select_' . str_replace('-', '_', self::$lang);
if (function_exists($plural_function)) {
$i = $plural_function($count);
} else {
$i = self::stringPluralSelectDefault($count);
}
// for some languages there is only a single array item
if (!isset($t[$i])) {
$s = $t[0];
} else {
$s = $t[$i];
}
} else {
$s = $t;
}
} elseif (self::stringPluralSelectDefault($count)) {
$s = $plural;
} else {
$s = $singular;
}
$s = @sprintf($s, $count);
return $s;
}
/**
* Provide a fallback which will not collide with a function defined in any language file
*/
private static function stringPluralSelectDefault($n)
{
return $n != 1;
return self::getClass(L10nClass::class)->tt($singular, $plural, $count);
}
/**
@ -329,19 +110,42 @@ class L10n extends BaseObject
*/
public static function getAvailableLanguages()
{
$langs = [];
$strings_file_paths = glob('view/lang/*/strings.php');
return L10nClass::getAvailableLanguages();
}
if (is_array($strings_file_paths) && count($strings_file_paths)) {
if (!in_array('view/lang/en/strings.php', $strings_file_paths)) {
$strings_file_paths[] = 'view/lang/en/strings.php';
}
asort($strings_file_paths);
foreach ($strings_file_paths as $strings_file_path) {
$path_array = explode('/', $strings_file_path);
$langs[$path_array[2]] = $path_array[2];
}
}
return $langs;
/**
* @brief Translate days and months names.
*
* @param string $s String with day or month name.
*
* @return string Translated string.
*/
public static function getDay($s)
{
return self::getClass(L10nClass::class)->getDay($s);
}
/**
* @brief Translate short days and months names.
*
* @param string $s String with short day or month name.
*
* @return string Translated string.
*/
public static function getDayShort($s)
{
return self::getClass(L10nClass::class)->getDayShort($s);
}
/**
* Load poke verbs
*
* @return array index is present tense verb
* value is array containing past tense verb, translation of present, translation of past
* @hook poke_verbs pokes array
*/
public static function getPokeVerbs()
{
return self::getClass(L10nClass::class)->getPokeVerbs();
}
}

459
src/Core/L10n/L10n.php Normal file
View file

@ -0,0 +1,459 @@
<?php
namespace Friendica\Core\L10n;
use Friendica\Core\Config\Configuration;
use Friendica\Core\Hook;
use Friendica\Core\Session;
use Friendica\Database\Database;
use Friendica\Util\Strings;
use Psr\Log\LoggerInterface;
/**
* Provide Language, Translation, and Localization functions to the application
* Localization can be referred to by the numeronym L10N (as in: "L", followed by ten more letters, and then "N").
*/
class L10n
{
/**
* A string indicating the current language used for translation:
* - Two-letter ISO 639-1 code.
* - Two-letter ISO 639-1 code + dash + Two-letter ISO 3166-1 alpha-2 country code.
*
* @var string
*/
private $lang = '';
/**
* A language code saved for later after pushLang() has been called.
*
* @var string
*/
private $langSave = '';
/**
* An array of translation strings whose key is the neutral english message.
*
* @var array
*/
private $strings = [];
/**
* An array of translation strings saved for later after pushLang() has been called.
*
* @var array
*/
private $stringsSave = [];
/**
* @var Database
*/
private $dba;
/**
* @var LoggerInterface
*/
private $logger;
public function __construct(Configuration $config, Database $dba, LoggerInterface $logger, array $server, array $get)
{
$this->dba = $dba;
$this->logger = $logger;
$this->loadTranslationTable(L10n::detectLanguage($server, $get, $config->get('system', 'language', 'en')));
}
/**
* Returns the current language code
*
* @return string Language code
*/
public function getCurrentLang()
{
return $this->lang;
}
/**
* Sets the language session variable
*/
public function setSessionVariable()
{
if (Session::get('authenticated') && !Session::get('language')) {
$_SESSION['language'] = $this->lang;
// we haven't loaded user data yet, but we need user language
if (Session::get('uid')) {
$user = $this->dba->selectFirst('user', ['language'], ['uid' => $_SESSION['uid']]);
if ($this->dba->isResult($user)) {
$_SESSION['language'] = $user['language'];
}
}
}
if (isset($_GET['lang'])) {
Session::set('language', $_GET['lang']);
}
}
public function setLangFromSession()
{
if (Session::get('language') !== $this->lang) {
$this->loadTranslationTable(Session::get('language'));
}
}
/**
* This function should be called before formatting messages in a specific target language
* different from the current user/system language.
*
* It saves the current translation strings in a separate variable and loads new translations strings.
*
* If called repeatedly, it won't save the translation strings again, just load the new ones.
*
* @param string $lang Language code
*
* @throws \Exception
* @see popLang()
* @brief Stores the current language strings and load a different language.
*/
public function pushLang($lang)
{
if ($lang === $this->lang) {
return;
}
if (empty($this->langSave)) {
$this->langSave = $this->lang;
$this->stringsSave = $this->strings;
}
$this->loadTranslationTable($lang);
}
/**
* Restores the original user/system language after having used pushLang()
*/
public function popLang()
{
if (!isset($this->langSave)) {
return;
}
$this->strings = $this->stringsSave;
$this->lang = $this->langSave;
$this->stringsSave = null;
$this->langSave = null;
}
/**
* Loads string translation table
*
* First addon strings are loaded, then globals
*
* Uses an App object shim since all the strings files refer to $a->strings
*
* @param string $lang language code to load
*
* @throws \Exception
*/
private function loadTranslationTable($lang)
{
$lang = Strings::sanitizeFilePathItem($lang);
// Don't override the language setting with empty languages
if (empty($lang)) {
return;
}
$a = new \stdClass();
$a->strings = [];
// load enabled addons strings
$addons = $this->dba->select('addon', ['name'], ['installed' => true]);
while ($p = $this->dba->fetch($addons)) {
$name = Strings::sanitizeFilePathItem($p['name']);
if (file_exists("addon/$name/lang/$lang/strings.php")) {
include __DIR__ . "/../../../addon/$name/lang/$lang/strings.php";
}
}
if (file_exists(__DIR__ . "/../../../view/lang/$lang/strings.php")) {
include __DIR__ . "/../../../view/lang/$lang/strings.php";
}
$this->lang = $lang;
$this->strings = $a->strings;
unset($a);
}
/**
* @brief Returns the preferred language from the HTTP_ACCEPT_LANGUAGE header
*
* @param string $sysLang The default fallback language
* @param array $server The $_SERVER array
* @param array $get The $_GET array
*
* @return string The two-letter language code
*/
public static function detectLanguage(array $server, array $get, string $sysLang = 'en')
{
$lang_variable = $server['HTTP_ACCEPT_LANGUAGE'] ?? null;
$acceptedLanguages = preg_split('/,\s*/', $lang_variable);
if (empty($acceptedLanguages)) {
$acceptedLanguages = [];
}
// Add get as absolute quality accepted language (except this language isn't valid)
if (!empty($get['lang'])) {
$acceptedLanguages[] = $get['lang'];
}
// return the sys language in case there's nothing to do
if (empty($acceptedLanguages)) {
return $sysLang;
}
// Set the syslang as default fallback
$current_lang = $sysLang;
// start with quality zero (every guessed language is more acceptable ..)
$current_q = 0;
foreach ($acceptedLanguages as $acceptedLanguage) {
$res = preg_match(
'/^([a-z]{1,8}(?:-[a-z]{1,8})*)(?:;\s*q=(0(?:\.[0-9]{1,3})?|1(?:\.0{1,3})?))?$/i',
$acceptedLanguage,
$matches
);
// Invalid language? -> skip
if (!$res) {
continue;
}
// split language codes based on it's "-"
$lang_code = explode('-', $matches[1]);
// determine the quality of the guess
if (isset($matches[2])) {
$lang_quality = (float)$matches[2];
} else {
// fallback so without a quality parameter, it's probably the best
$lang_quality = 1;
}
// loop through each part of the code-parts
while (count($lang_code)) {
// try to mix them so we can get double-code parts too
$match_lang = strtolower(join('-', $lang_code));
if (file_exists(__DIR__ . "/../../../view/lang/$match_lang") &&
is_dir(__DIR__ . "/../../../view/lang/$match_lang")) {
if ($lang_quality > $current_q) {
$current_lang = $match_lang;
$current_q = $lang_quality;
break;
}
}
// remove the most right code-part
array_pop($lang_code);
}
}
return $current_lang;
}
/**
* @brief Return the localized version of the provided string with optional string interpolation
*
* This function takes a english string as parameter, and if a localized version
* exists for the current language, substitutes it before performing an eventual
* string interpolation (sprintf) with additional optional arguments.
*
* Usages:
* - L10n::t('This is an example')
* - L10n::t('URL %s returned no result', $url)
* - L10n::t('Current version: %s, new version: %s', $current_version, $new_version)
*
* @param string $s
* @param array $vars Variables to interpolate in the translation string
*
* @return string
*/
public function t($s, ...$vars)
{
if (empty($s)) {
return '';
}
if (!empty($this->strings[$s])) {
$t = $this->strings[$s];
$s = is_array($t) ? $t[0] : $t;
}
if (count($vars) > 0) {
$s = sprintf($s, ...$vars);
}
return $s;
}
/**
* @brief Return the localized version of a singular/plural string with optional string interpolation
*
* This function takes two english strings as parameters, singular and plural, as
* well as a count. If a localized version exists for the current language, they
* are used instead. Discrimination between singular and plural is done using the
* localized function if any or the default one. Finally, a string interpolation
* is performed using the count as parameter.
*
* Usages:
* - L10n::tt('Like', 'Likes', $count)
* - L10n::tt("%s user deleted", "%s users deleted", count($users))
*
* @param string $singular
* @param string $plural
* @param int $count
*
* @return string
* @throws \Exception
*/
public function tt(string $singular, string $plural, int $count)
{
if (!empty($this->strings[$singular])) {
$t = $this->strings[$singular];
if (is_array($t)) {
$plural_function = 'string_plural_select_' . str_replace('-', '_', $this->lang);
if (function_exists($plural_function)) {
$i = $plural_function($count);
} else {
$i = $this->stringPluralSelectDefault($count);
}
// for some languages there is only a single array item
if (!isset($t[$i])) {
$s = $t[0];
} else {
$s = $t[$i];
}
} else {
$s = $t;
}
} elseif ($this->stringPluralSelectDefault($count)) {
$s = $plural;
} else {
$s = $singular;
}
$s = @sprintf($s, $count);
return $s;
}
/**
* Provide a fallback which will not collide with a function defined in any language file
*
* @param int $n
*
* @return bool
*/
private function stringPluralSelectDefault($n)
{
return $n != 1;
}
/**
* Return installed languages codes as associative array
*
* Scans the view/lang directory for the existence of "strings.php" files, and
* returns an alphabetical list of their folder names (@-char language codes).
* Adds the english language if it's missing from the list.
*
* Ex: array('de' => 'de', 'en' => 'en', 'fr' => 'fr', ...)
*
* @return array
*/
public static function getAvailableLanguages()
{
$langs = [];
$strings_file_paths = glob('view/lang/*/strings.php');
if (is_array($strings_file_paths) && count($strings_file_paths)) {
if (!in_array('view/lang/en/strings.php', $strings_file_paths)) {
$strings_file_paths[] = 'view/lang/en/strings.php';
}
asort($strings_file_paths);
foreach ($strings_file_paths as $strings_file_path) {
$path_array = explode('/', $strings_file_path);
$langs[$path_array[2]] = $path_array[2];
}
}
return $langs;
}
/**
* Translate days and months names.
*
* @param string $s String with day or month name.
*
* @return string Translated string.
*/
public function getDay($s)
{
$ret = str_replace(['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'],
[$this->t('Monday'), $this->t('Tuesday'), $this->t('Wednesday'), $this->t('Thursday'), $this->t('Friday'), $this->t('Saturday'), $this->t('Sunday')],
$s);
$ret = str_replace(['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
[$this->t('January'), $this->t('February'), $this->t('March'), $this->t('April'), $this->t('May'), $this->t('June'), $this->t('July'), $this->t('August'), $this->t('September'), $this->t('October'), $this->t('November'), $this->t('December')],
$ret);
return $ret;
}
/**
* Translate short days and months names.
*
* @param string $s String with short day or month name.
*
* @return string Translated string.
*/
public function getDayShort($s)
{
$ret = str_replace(['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
[$this->t('Mon'), $this->t('Tue'), $this->t('Wed'), $this->t('Thu'), $this->t('Fri'), $this->t('Sat'), $this->t('Sun')],
$s);
$ret = str_replace(['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
[$this->t('Jan'), $this->t('Feb'), $this->t('Mar'), $this->t('Apr'), $this->t('May'), $this->t('Jun'), $this->t('Jul'), $this->t('Aug'), $this->t('Sep'), $this->t('Oct'), $this->t('Nov'), $this->t('Dec')],
$ret);
return $ret;
}
/**
* Load poke verbs
*
* @return array index is present tense verb
* value is array containing past tense verb, translation of present, translation of past
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @hook poke_verbs pokes array
*/
public function getPokeVerbs()
{
// index is present tense verb
// value is array containing past tense verb, translation of present, translation of past
$arr = [
'poke' => ['poked', $this->t('poke'), $this->t('poked')],
'ping' => ['pinged', $this->t('ping'), $this->t('pinged')],
'prod' => ['prodded', $this->t('prod'), $this->t('prodded')],
'slap' => ['slapped', $this->t('slap'), $this->t('slapped')],
'finger' => ['fingered', $this->t('finger'), $this->t('fingered')],
'rebuff' => ['rebuffed', $this->t('rebuff'), $this->t('rebuffed')],
];
Hook::callAll('poke_verbs', $arr);
return $arr;
}
}

View file

@ -1,140 +1,57 @@
<?php
namespace Friendica\Core;
/**
* @file src/Core/Lock.php
* @brief Functions for preventing parallel execution of functions
*/
use Friendica\Core\Cache\CacheDriverFactory;
use Friendica\Core\Cache\IMemoryCacheDriver;
namespace Friendica\Core;
use Friendica\BaseObject;
use Friendica\Core\Cache\Cache;
use Friendica\Core\Lock\ILock;
/**
* @brief This class contain Functions for preventing parallel execution of functions
* This class contain Functions for preventing parallel execution of functions
*/
class Lock
class Lock extends BaseObject
{
/**
* @var Lock\ILockDriver;
*/
static $driver = null;
public static function init()
{
$lock_driver = Config::get('system', 'lock_driver', 'default');
try {
switch ($lock_driver) {
case 'memcache':
case 'memcached':
case 'redis':
$cache_driver = CacheDriverFactory::create($lock_driver);
if ($cache_driver instanceof IMemoryCacheDriver) {
self::$driver = new Lock\CacheLockDriver($cache_driver);
}
break;
case 'database':
self::$driver = new Lock\DatabaseLockDriver();
break;
case 'semaphore':
self::$driver = new Lock\SemaphoreLockDriver();
break;
default:
self::useAutoDriver();
}
} catch (\Exception $exception) {
logger ('Driver \'' . $lock_driver . '\' failed - Fallback to \'useAutoDriver()\'');
self::useAutoDriver();
}
}
/**
* @brief This method tries to find the best - local - locking method for Friendica
*
* The following sequence will be tried:
* 1. Semaphore Locking
* 2. Cache Locking
* 3. Database Locking
*
*/
private static function useAutoDriver() {
// 1. Try to use Semaphores for - local - locking
if (function_exists('sem_get')) {
try {
self::$driver = new Lock\SemaphoreLockDriver();
return;
} catch (\Exception $exception) {
logger ('Using Semaphore driver for locking failed: ' . $exception->getMessage());
}
}
// 2. Try to use Cache Locking (don't use the DB-Cache Locking because it works different!)
$cache_driver = Config::get('system', 'cache_driver', 'database');
if ($cache_driver != 'database') {
try {
$lock_driver = CacheDriverFactory::create($cache_driver);
if ($lock_driver instanceof IMemoryCacheDriver) {
self::$driver = new Lock\CacheLockDriver($lock_driver);
}
return;
} catch (\Exception $exception) {
logger('Using Cache driver for locking failed: ' . $exception->getMessage());
}
}
// 3. Use Database Locking as a Fallback
self::$driver = new Lock\DatabaseLockDriver();
}
/**
* Returns the current cache driver
*
* @return Lock\ILockDriver;
*/
private static function getDriver()
{
if (self::$driver === null) {
self::init();
}
return self::$driver;
}
/**
* @brief Acquires a lock for a given name
*
* @param string $key Name of the lock
* @param string $key Name of the lock
* @param integer $timeout Seconds until we give up
* @param integer $ttl The Lock lifespan, must be one of the Cache constants
*
* @return boolean Was the lock successful?
* @throws \Exception
*/
public static function acquire($key, $timeout = 120)
public static function acquire($key, $timeout = 120, $ttl = Cache::FIVE_MINUTES)
{
return self::getDriver()->acquireLock($key, $timeout);
return self::getClass(ILock::class)->acquireLock($key, $timeout, $ttl);
}
/**
* @brief Releases a lock if it was set by us
*
* @param string $key Name of the lock
* @param string $key Name of the lock
* @param bool $override Overrides the lock to get releases
*
* @return void
* @throws \Exception
*/
public static function release($key)
public static function release($key, $override = false)
{
self::getDriver()->releaseLock($key);
return self::getClass(ILock::class)->releaseLock($key, $override);
}
/**
* @brief Releases all lock that were set by us
* @return void
* @throws \Exception
*/
public static function releaseAll()
{
self::getDriver()->releaseAll();
self::getClass(ILock::class)->releaseAll();
}
}

142
src/Core/Lock/CacheLock.php Normal file
View file

@ -0,0 +1,142 @@
<?php
namespace Friendica\Core\Lock;
use Friendica\Core\Cache;
use Friendica\Core\Cache\IMemoryCache;
class CacheLock extends Lock
{
/**
* @var string The static prefix of all locks inside the cache
*/
const CACHE_PREFIX = 'lock:';
/**
* @var \Friendica\Core\Cache\ICache;
*/
private $cache;
/**
* CacheLock constructor.
*
* @param IMemoryCache $cache The CacheDriver for this type of lock
*/
public function __construct(IMemoryCache $cache)
{
$this->cache = $cache;
}
/**
* (@inheritdoc)
*/
public function acquireLock($key, $timeout = 120, $ttl = Cache\Cache::FIVE_MINUTES)
{
$got_lock = false;
$start = time();
$cachekey = self::getLockKey($key);
do {
$lock = $this->cache->get($cachekey);
// When we do want to lock something that was already locked by us.
if ((int)$lock == getmypid()) {
$got_lock = true;
}
// When we do want to lock something new
if (is_null($lock)) {
// At first initialize it with "0"
$this->cache->add($cachekey, 0);
// Now the value has to be "0" because otherwise the key was used by another process meanwhile
if ($this->cache->compareSet($cachekey, 0, getmypid(), $ttl)) {
$got_lock = true;
$this->markAcquire($key);
}
}
if (!$got_lock && ($timeout > 0)) {
usleep(rand(10000, 200000));
}
} while (!$got_lock && ((time() - $start) < $timeout));
return $got_lock;
}
/**
* (@inheritdoc)
*/
public function releaseLock($key, $override = false)
{
$cachekey = self::getLockKey($key);
if ($override) {
$return = $this->cache->delete($cachekey);
} else {
$return = $this->cache->compareDelete($cachekey, getmypid());
}
$this->markRelease($key);
return $return;
}
/**
* (@inheritdoc)
*/
public function isLocked($key)
{
$cachekey = self::getLockKey($key);
$lock = $this->cache->get($cachekey);
return isset($lock) && ($lock !== false);
}
/**
* {@inheritDoc}
*/
public function getName()
{
return $this->cache->getName();
}
/**
* {@inheritDoc}
*/
public function getLocks(string $prefix = '')
{
$locks = $this->cache->getAllKeys(self::CACHE_PREFIX . $prefix);
array_walk($locks, function (&$lock, $key) {
$lock = substr($lock, strlen(self::CACHE_PREFIX));
});
return $locks;
}
/**
* {@inheritDoc}
*/
public function releaseAll($override = false)
{
$success = parent::releaseAll($override);
$locks = $this->getLocks();
foreach ($locks as $lock) {
if (!$this->releaseLock($lock, $override)) {
$success = false;
}
}
return $success;
}
/**
* @param string $key The original key
*
* @return string The cache key used for the cache
*/
private static function getLockKey($key)
{
return self::CACHE_PREFIX . $key;
}
}

View file

@ -1,89 +0,0 @@
<?php
namespace Friendica\Core\Lock;
use Friendica\Core\Cache;
use Friendica\Core\Cache\IMemoryCacheDriver;
class CacheLockDriver extends AbstractLockDriver
{
/**
* @var \Friendica\Core\Cache\ICacheDriver;
*/
private $cache;
/**
* CacheLockDriver constructor.
*
* @param IMemoryCacheDriver $cache The CacheDriver for this type of lock
*/
public function __construct(IMemoryCacheDriver $cache)
{
$this->cache = $cache;
}
/**
* (@inheritdoc)
*/
public function acquireLock($key, $timeout = 120, $ttl = Cache::FIVE_MINUTES)
{
$got_lock = false;
$start = time();
$cachekey = self::getLockKey($key);
do {
$lock = $this->cache->get($cachekey);
// When we do want to lock something that was already locked by us.
if ((int)$lock == getmypid()) {
$got_lock = true;
}
// When we do want to lock something new
if (is_null($lock)) {
// At first initialize it with "0"
$this->cache->add($cachekey, 0);
// Now the value has to be "0" because otherwise the key was used by another process meanwhile
if ($this->cache->compareSet($cachekey, 0, getmypid(), $ttl)) {
$got_lock = true;
$this->markAcquire($key);
}
}
if (!$got_lock && ($timeout > 0)) {
usleep(rand(10000, 200000));
}
} while (!$got_lock && ((time() - $start) < $timeout));
return $got_lock;
}
/**
* (@inheritdoc)
*/
public function releaseLock($key)
{
$cachekey = self::getLockKey($key);
$this->cache->compareDelete($cachekey, getmypid());
$this->markRelease($key);
}
/**
* (@inheritdoc)
*/
public function isLocked($key)
{
$cachekey = self::getLockKey($key);
$lock = $this->cache->get($cachekey);
return isset($lock) && ($lock !== false);
}
/**
* @param string $key The original key
* @return string The cache key used for the cache
*/
private static function getLockKey($key) {
return "lock:" . $key;
}
}

View file

@ -0,0 +1,158 @@
<?php
namespace Friendica\Core\Lock;
use Friendica\Core\Cache;
use Friendica\Database\Database;
use Friendica\Util\DateTimeFormat;
/**
* Locking driver that stores the locks in the database
*/
class DatabaseLock extends Lock
{
/**
* The current ID of the process
*
* @var int
*/
private $pid;
/**
* @var Database The database connection of Friendica
*/
private $dba;
/**
* @param null|int $pid The Id of the current process (null means determine automatically)
*/
public function __construct(Database $dba, $pid = null)
{
$this->dba = $dba;
$this->pid = isset($pid) ? $pid : getmypid();
}
/**
* (@inheritdoc)
*/
public function acquireLock($key, $timeout = 120, $ttl = Cache::FIVE_MINUTES)
{
$got_lock = false;
$start = time();
do {
$this->dba->lock('locks');
$lock = $this->dba->selectFirst('locks', ['locked', 'pid'], ['`name` = ? AND `expires` >= ?', $key, DateTimeFormat::utcNow()]);
if ($this->dba->isResult($lock)) {
if ($lock['locked']) {
// We want to lock something that was already locked by us? So we got the lock.
if ($lock['pid'] == $this->pid) {
$got_lock = true;
}
}
if (!$lock['locked']) {
$this->dba->update('locks', ['locked' => true, 'pid' => $this->pid, 'expires' => DateTimeFormat::utc('now + ' . $ttl . 'seconds')], ['name' => $key]);
$got_lock = true;
}
} else {
$this->dba->insert('locks', ['name' => $key, 'locked' => true, 'pid' => $this->pid, 'expires' => DateTimeFormat::utc('now + ' . $ttl . 'seconds')]);
$got_lock = true;
$this->markAcquire($key);
}
$this->dba->unlock();
if (!$got_lock && ($timeout > 0)) {
usleep(rand(100000, 2000000));
}
} while (!$got_lock && ((time() - $start) < $timeout));
return $got_lock;
}
/**
* (@inheritdoc)
*/
public function releaseLock($key, $override = false)
{
if ($override) {
$where = ['name' => $key];
} else {
$where = ['name' => $key, 'pid' => $this->pid];
}
if ($this->dba->exists('locks', $where)) {
$return = $this->dba->delete('locks', $where);
} else {
$return = false;
}
$this->markRelease($key);
return $return;
}
/**
* (@inheritdoc)
*/
public function releaseAll($override = false)
{
$success = parent::releaseAll($override);
if ($override) {
$where = ['1 = 1'];
} else {
$where = ['pid' => $this->pid];
}
$return = $this->dba->delete('locks', $where);
$this->acquiredLocks = [];
return $return && $success;
}
/**
* (@inheritdoc)
*/
public function isLocked($key)
{
$lock = $this->dba->selectFirst('locks', ['locked'], ['`name` = ? AND `expires` >= ?', $key, DateTimeFormat::utcNow()]);
if ($this->dba->isResult($lock)) {
return $lock['locked'] !== false;
} else {
return false;
}
}
/**
* {@inheritDoc}
*/
public function getName()
{
return self::TYPE_DATABASE;
}
/**
* {@inheritDoc}
*/
public function getLocks(string $prefix = '')
{
if (empty($prefix)) {
$where = ['`expires` >= ?', DateTimeFormat::utcNow()];
} else {
$where = ['`expires` >= ? AND `name` LIKE CONCAT(?, \'%\')', DateTimeFormat::utcNow(), $prefix];
}
$stmt = $this->dba->select('locks', ['name'], $where);
$keys = [];
while ($key = $this->dba->fetch($stmt)) {
array_push($keys, $key['name']);
}
$this->dba->close($stmt);
return $keys;
}
}

View file

@ -1,88 +0,0 @@
<?php
namespace Friendica\Core\Lock;
use Friendica\Core\Cache;
use Friendica\Database\DBA;
use Friendica\Util\DateTimeFormat;
/**
* Locking driver that stores the locks in the database
*/
class DatabaseLockDriver extends AbstractLockDriver
{
/**
* (@inheritdoc)
*/
public function acquireLock($key, $timeout = 120, $ttl = Cache::FIVE_MINUTES)
{
$got_lock = false;
$start = time();
do {
DBA::lock('locks');
$lock = DBA::selectFirst('locks', ['locked', 'pid'], ['`name` = ? AND `expires` >= ?', $key, DateTimeFormat::utcNow()]);
if (DBA::isResult($lock)) {
if ($lock['locked']) {
// We want to lock something that was already locked by us? So we got the lock.
if ($lock['pid'] == getmypid()) {
$got_lock = true;
}
}
if (!$lock['locked']) {
DBA::update('locks', ['locked' => true, 'pid' => getmypid(), 'expires' => DateTimeFormat::utc('now + ' . $ttl . 'seconds')], ['name' => $key]);
$got_lock = true;
}
} else {
DBA::insert('locks', ['name' => $key, 'locked' => true, 'pid' => getmypid(), 'expires' => DateTimeFormat::utc('now + ' . $ttl . 'seconds')]);
$got_lock = true;
$this->markAcquire($key);
}
DBA::unlock();
if (!$got_lock && ($timeout > 0)) {
usleep(rand(100000, 2000000));
}
} while (!$got_lock && ((time() - $start) < $timeout));
return $got_lock;
}
/**
* (@inheritdoc)
*/
public function releaseLock($key)
{
DBA::delete('locks', ['name' => $key, 'pid' => getmypid()]);
$this->markRelease($key);
return;
}
/**
* (@inheritdoc)
*/
public function releaseAll()
{
DBA::delete('locks', ['pid' => getmypid()]);
$this->acquiredLocks = [];
}
/**
* (@inheritdoc)
*/
public function isLocked($key)
{
$lock = DBA::selectFirst('locks', ['locked'], ['`name` = ? AND `expires` >= ?', $key, DateTimeFormat::utcNow()]);
if (DBA::isResult($lock)) {
return $lock['locked'] !== false;
} else {
return false;
}
}
}

69
src/Core/Lock/ILock.php Normal file
View file

@ -0,0 +1,69 @@
<?php
namespace Friendica\Core\Lock;
use Friendica\Core\Cache;
/**
* Lock Interface
*
* @author Philipp Holzer <admin@philipp.info>
*/
interface ILock
{
/**
* Checks, if a key is currently locked to a or my process
*
* @param string $key The name of the lock
*
* @return bool
*/
public function isLocked($key);
/**
*
* Acquires a lock for a given name
*
* @param string $key The Name of the lock
* @param integer $timeout Seconds until we give up
* @param integer $ttl Seconds The lock lifespan, must be one of the Cache constants
*
* @return boolean Was the lock successful?
*/
public function acquireLock($key, $timeout = 120, $ttl = Cache\Cache::FIVE_MINUTES);
/**
* Releases a lock if it was set by us
*
* @param string $key The Name of the lock
* @param bool $override Overrides the lock to get released
*
* @return boolean Was the unlock successful?
*/
public function releaseLock($key, $override = false);
/**
* Releases all lock that were set by us
*
* @param bool $override Override to release all locks
*
* @return boolean Was the unlock of all locks successful?
*/
public function releaseAll($override = false);
/**
* Returns the name of the current lock
*
* @return string
*/
public function getName();
/**
* Lists all locks
*
* @param string prefix optional a prefix to search
*
* @return array Empty if it isn't supported by the cache driver
*/
public function getLocks(string $prefix = '');
}

View file

@ -1,48 +0,0 @@
<?php
namespace Friendica\Core\Lock;
use Friendica\Core\Cache;
/**
* Lock Driver Interface
*
* @author Philipp Holzer <admin@philipp.info>
*/
interface ILockDriver
{
/**
* Checks, if a key is currently locked to a or my process
*
* @param string $key The name of the lock
* @return bool
*/
public function isLocked($key);
/**
*
* Acquires a lock for a given name
*
* @param string $key The Name of the lock
* @param integer $timeout Seconds until we give up
* @param integer $ttl Seconds The lock lifespan, must be one of the Cache constants
*
* @return boolean Was the lock successful?
*/
public function acquireLock($key, $timeout = 120, $ttl = Cache::FIVE_MINUTES);
/**
* Releases a lock if it was set by us
*
* @param string $key The Name of the lock
*
* @return void
*/
public function releaseLock($key);
/**
* Releases all lock that were set by us
*
* @return void
*/
public function releaseAll();
}

View file

@ -1,17 +1,21 @@
<?php
namespace Friendica\Core\Lock;
use Friendica\BaseObject;
use Friendica\Core\Cache\Cache;
/**
* Class AbstractLockDriver
* Class AbstractLock
*
* @package Friendica\Core\Lock
*
* Basic class for Locking with common functions (local acquired locks, releaseAll, ..)
*/
abstract class AbstractLockDriver extends BaseObject implements ILockDriver
abstract class Lock implements ILock
{
const TYPE_DATABASE = Cache::TYPE_DATABASE;
const TYPE_SEMAPHORE = 'semaphore';
/**
* @var array The local acquired locks
*/
@ -21,9 +25,11 @@ abstract class AbstractLockDriver extends BaseObject implements ILockDriver
* Check if we've locally acquired a lock
*
* @param string key The Name of the lock
*
* @return bool Returns true if the lock is set
*/
protected function hasAcquiredLock($key) {
protected function hasAcquiredLock($key)
{
return isset($this->acquireLock[$key]) && $this->acquiredLocks[$key] === true;
}
@ -32,7 +38,8 @@ abstract class AbstractLockDriver extends BaseObject implements ILockDriver
*
* @param string $key The Name of the lock
*/
protected function markAcquire($key) {
protected function markAcquire($key)
{
$this->acquiredLocks[$key] = true;
}
@ -41,18 +48,24 @@ abstract class AbstractLockDriver extends BaseObject implements ILockDriver
*
* @param string $key The Name of the lock
*/
protected function markRelease($key) {
protected function markRelease($key)
{
unset($this->acquiredLocks[$key]);
}
/**
* Releases all lock that were set by us
*
* @return void
* {@inheritDoc}
*/
public function releaseAll() {
public function releaseAll($override = false)
{
$return = true;
foreach ($this->acquiredLocks as $acquiredLock => $hasLock) {
$this->releaseLock($acquiredLock);
if (!$this->releaseLock($acquiredLock, $override)) {
$return = false;
}
}
return $return;
}
}

View file

@ -0,0 +1,124 @@
<?php
namespace Friendica\Core\Lock;
use Friendica\Core\Cache;
class SemaphoreLock extends Lock
{
private static $semaphore = [];
public function __construct()
{
if (!function_exists('sem_get')) {
throw new \Exception('Semaphore lock not supported');
}
}
/**
* (@inheritdoc)
*/
private static function semaphoreKey($key)
{
$success = true;
$temp = get_temppath();
$file = $temp . '/' . $key . '.sem';
if (!file_exists($file)) {
$success = !empty(file_put_contents($file, $key));
}
return $success ? ftok($file, 'f') : false;
}
/**
* (@inheritdoc)
*/
public function acquireLock($key, $timeout = 120, $ttl = Cache\Cache::FIVE_MINUTES)
{
self::$semaphore[$key] = sem_get(self::semaphoreKey($key));
if (!empty(self::$semaphore[$key])) {
if ((bool)sem_acquire(self::$semaphore[$key], ($timeout === 0))) {
$this->markAcquire($key);
return true;
}
}
return false;
}
/**
* (@inheritdoc)
*
* @param bool $override not necessary parameter for semaphore locks since the lock lives as long as the execution
* of the using function
*/
public function releaseLock($key, $override = false)
{
$success = false;
if (!empty(self::$semaphore[$key])) {
try {
$success = @sem_release(self::$semaphore[$key]);
unset(self::$semaphore[$key]);
$this->markRelease($key);
} catch (\Exception $exception) {
$success = false;
}
}
return $success;
}
/**
* (@inheritdoc)
*/
public function isLocked($key)
{
return isset(self::$semaphore[$key]);
}
/**
* {@inheritDoc}
*/
public function getName()
{
return self::TYPE_SEMAPHORE;
}
/**
* {@inheritDoc}
*/
public function getLocks(string $prefix = '')
{
// We can just return our own semaphore keys, since we don't know
// the state of other semaphores, even if the .sem files exists
$keys = array_keys(self::$semaphore);
if (empty($prefix)) {
return $keys;
} else {
$result = [];
foreach ($keys as $key) {
if (strpos($key, $prefix) === 0) {
array_push($result, $key);
}
}
return $result;
}
}
/**
* {@inheritDoc}
*/
public function releaseAll($override = false)
{
// Semaphores are just alive during a run, so there is no need to release
// You can just release your own locks
return parent::releaseAll($override);
}
}

View file

@ -1,72 +0,0 @@
<?php
namespace Friendica\Core\Lock;
use Friendica\Core\Cache;
class SemaphoreLockDriver extends AbstractLockDriver
{
private static $semaphore = [];
public function __construct()
{
if (!function_exists('sem_get')) {
throw new \Exception('Semaphore lock not supported');
}
}
/**
* (@inheritdoc)
*/
private static function semaphoreKey($key)
{
$temp = get_temppath();
$file = $temp . '/' . $key . '.sem';
if (!file_exists($file)) {
file_put_contents($file, $key);
}
return ftok($file, 'f');
}
/**
* (@inheritdoc)
*/
public function acquireLock($key, $timeout = 120, $ttl = Cache::FIVE_MINUTES)
{
self::$semaphore[$key] = sem_get(self::semaphoreKey($key));
if (self::$semaphore[$key]) {
if (sem_acquire(self::$semaphore[$key], ($timeout == 0))) {
$this->markAcquire($key);
return true;
}
}
return false;
}
/**
* (@inheritdoc)
*/
public function releaseLock($key)
{
if (empty(self::$semaphore[$key])) {
return false;
} else {
$success = @sem_release(self::$semaphore[$key]);
unset(self::$semaphore[$key]);
$this->markRelease($key);
return $success;
}
}
/**
* (@inheritdoc)
*/
public function isLocked($key)
{
return isset(self::$semaphore[$key]);
}
}

250
src/Core/Logger.php Normal file
View file

@ -0,0 +1,250 @@
<?php
/**
* @file src/Core/Logger.php
*/
namespace Friendica\Core;
use Friendica\BaseObject;
use Friendica\Util\Logger\WorkerLogger;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
/**
* @brief Logger functions
*/
class Logger extends BaseObject
{
/**
* @see Logger::error()
*/
const WARNING = LogLevel::ERROR;
/**
* @see Logger::warning()
*/
const INFO = LogLevel::WARNING;
/**
* @see Logger::notice()
*/
const TRACE = LogLevel::NOTICE;
/**
* @see Logger::info()
*/
const DEBUG = LogLevel::INFO;
/**
* @see Logger::debug()
*/
const DATA = LogLevel::DEBUG;
/**
* @see Logger::debug()
*/
const ALL = LogLevel::DEBUG;
/**
* @var LoggerInterface The default Logger type
*/
const TYPE_LOGGER = LoggerInterface::class;
/**
* @var WorkerLogger A specific worker logger type, which can be anabled
*/
const TYPE_WORKER = WorkerLogger::class;
/**
* @var LoggerInterface The current logger type
*/
private static $type = self::TYPE_LOGGER;
/**
* @var array the legacy loglevels
* @deprecated 2019.03 use PSR-3 loglevels
* @see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md#5-psrlogloglevel
*
*/
public static $levels = [
self::WARNING => 'Warning',
self::INFO => 'Info',
self::TRACE => 'Trace',
self::DEBUG => 'Debug',
self::DATA => 'Data',
];
/**
* Enable additional logging for worker usage
*
* @param string $functionName The worker function, which got called
*
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function enableWorker(string $functionName)
{
self::$type = self::TYPE_WORKER;
self::getClass(self::$type)->setFunctionName($functionName);
}
/**
* Disable additional logging for worker usage
*/
public static function disableWorker()
{
self::$type = self::TYPE_LOGGER;
}
/**
* System is unusable.
*
* @see LoggerInterface::emergency()
*
* @param string $message
* @param array $context
*
* @return void
* @throws \Exception
*/
public static function emergency($message, $context = [])
{
self::getClass(self::$type)->emergency($message, $context);
}
/**
* Action must be taken immediately.
* @see LoggerInterface::alert()
*
* Example: Entire website down, database unavailable, etc. This should
* trigger the SMS alerts and wake you up.
*
* @param string $message
* @param array $context
*
* @return void
* @throws \Exception
*/
public static function alert($message, $context = [])
{
self::getClass(self::$type)->alert($message, $context);
}
/**
* Critical conditions.
* @see LoggerInterface::critical()
*
* Example: Application component unavailable, unexpected exception.
*
* @param string $message
* @param array $context
*
* @return void
* @throws \Exception
*/
public static function critical($message, $context = [])
{
self::getClass(self::$type)->critical($message, $context);
}
/**
* Runtime errors that do not require immediate action but should typically
* be logged and monitored.
* @see LoggerInterface::error()
*
* @param string $message
* @param array $context
*
* @return void
* @throws \Exception
*/
public static function error($message, $context = [])
{
self::getClass(self::$type)->error($message, $context);
}
/**
* Exceptional occurrences that are not errors.
* @see LoggerInterface::warning()
*
* Example: Use of deprecated APIs, poor use of an API, undesirable things
* that are not necessarily wrong.
*
* @param string $message
* @param array $context
*
* @return void
* @throws \Exception
*/
public static function warning($message, $context = [])
{
self::getClass(self::$type)->warning($message, $context);
}
/**
* Normal but significant events.
* @see LoggerInterface::notice()
*
* @param string $message
* @param array $context
*
* @return void
* @throws \Exception
*/
public static function notice($message, $context = [])
{
self::getClass(self::$type)->notice($message, $context);
}
/**
* Interesting events.
* @see LoggerInterface::info()
*
* Example: User logs in, SQL logs.
*
* @param string $message
* @param array $context
*
* @return void
* @throws \Exception
*/
public static function info($message, $context = [])
{
self::getClass(self::$type)->info($message, $context);
}
/**
* Detailed debug information.
* @see LoggerInterface::debug()
*
* @param string $message
* @param array $context
*
* @return void
* @throws \Exception
*/
public static function debug($message, $context = [])
{
self::getClass(self::$type)->debug($message, $context);
}
/**
* @brief Logs the given message at the given log level
*
* @param string $msg
* @param string $level
*
* @throws \Exception
* @deprecated since 2019.03 Use Logger::debug() Logger::info() , ... instead
*/
public static function log($msg, $level = LogLevel::INFO)
{
self::getClass(self::$type)->log($level, $msg);
}
/**
* @brief An alternative logger for development.
* Works largely as log() but allows developers
* to isolate particular elements they are targetting
* personally without background noise
*
* @param string $msg
* @param string $level
* @throws \Exception
*/
public static function devLog($msg, $level = LogLevel::DEBUG)
{
self::getClass('$devLogger')->log($level, $msg);
}
}

View file

@ -9,7 +9,6 @@ namespace Friendica\Core;
use Friendica\BaseObject;
use Friendica\Content\Text\BBCode;
use Friendica\Content\Text\HTML;
use Friendica\Core\Protocol;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Model\Item;
@ -18,8 +17,6 @@ use Friendica\Util\Proxy as ProxyUtils;
use Friendica\Util\Temporal;
use Friendica\Util\XML;
require_once 'include/dba.php';
/**
* @brief Methods for read and write notifications from/to database
* or for formatting notifications
@ -37,8 +34,9 @@ class NotificationsManager extends BaseObject
* - date_rel : relative date string
* - msg_html: message as html string
* - msg_plain: message as plain text string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private function _set_extra($notes)
private function _set_extra(array $notes)
{
$rets = [];
foreach ($notes as $n) {
@ -57,49 +55,28 @@ class NotificationsManager extends BaseObject
* @brief Get all notifications for local_user()
*
* @param array $filter optional Array "column name"=>value: filter query by columns values
* @param string $order optional Space separated list of column to sort by.
* Prepend name with "+" to sort ASC, "-" to sort DESC. Default to "-date"
* @param array $order optional Array to order by
* @param string $limit optional Query limits
*
* @return array of results or false on errors
* @return array|bool of results or false on errors
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public function getAll($filter = [], $order = "-date", $limit = "")
public function getAll($filter = [], $order = ['date' => 'DESC'], $limit = "")
{
$filter_str = [];
$filter_sql = "";
foreach ($filter as $column => $value) {
$filter_str[] = sprintf("`%s` = '%s'", $column, DBA::escape($value));
}
if (count($filter_str) > 0) {
$filter_sql = "AND " . implode(" AND ", $filter_str);
$params = [];
$params['order'] = $order;
if (!empty($limit)) {
$params['limit'] = $limit;
}
$aOrder = explode(" ", $order);
$asOrder = [];
foreach ($aOrder as $o) {
$dir = "asc";
if ($o[0] === "-") {
$dir = "desc";
$o = substr($o, 1);
}
if ($o[0] === "+") {
$dir = "asc";
$o = substr($o, 1);
}
$asOrder[] = "$o $dir";
}
$order_sql = implode(", ", $asOrder);
$dbFilter = array_merge($filter, ['uid' => local_user()]);
if ($limit != "") {
$limit = " LIMIT " . $limit;
}
$r = q(
"SELECT * FROM `notify` WHERE `uid` = %d $filter_sql ORDER BY $order_sql $limit",
intval(local_user())
);
$stmtNotifies = DBA::select('notify', [], $dbFilter, $params);
if (DBA::isResult($r)) {
return $this->_set_extra($r);
if (DBA::isResult($stmtNotifies)) {
return $this->_set_extra(DBA::toArray($stmtNotifies));
}
return false;
@ -110,16 +87,13 @@ class NotificationsManager extends BaseObject
*
* @param int $id identity
* @return array note values or null if not found
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public function getByID($id)
{
$r = q(
"SELECT * FROM `notify` WHERE `id` = %d AND `uid` = %d LIMIT 1",
intval($id),
intval(local_user())
);
if (DBA::isResult($r)) {
return $this->_set_extra($r)[0];
$stmtNotify = DBA::selectFirst('notify', [], ['id' => $id, 'uid' => local_user()]);
if (DBA::isResult($stmtNotify)) {
return $this->_set_extra([$stmtNotify])[0];
}
return null;
}
@ -130,17 +104,17 @@ class NotificationsManager extends BaseObject
* @param array $note note array
* @param bool $seen optional true or false, default true
* @return bool true on success, false on errors
* @throws \Exception
*/
public function setSeen($note, $seen = true)
{
return q(
"UPDATE `notify` SET `seen` = %d WHERE (`link` = '%s' OR (`parent` != 0 AND `parent` = %d AND `otype` = '%s')) AND `uid` = %d",
intval($seen),
DBA::escape($note['link']),
intval($note['parent']),
DBA::escape($note['otype']),
intval(local_user())
);
return DBA::update('notify', ['seen' => $seen], [
'(`link` = ? OR (`parent` != 0 AND `parent` = ? AND `otype` = ?)) AND `uid` = ?',
$note['link'],
$note['parent'],
$note['otype'],
local_user()
]);
}
/**
@ -148,20 +122,18 @@ class NotificationsManager extends BaseObject
*
* @param bool $seen optional true or false. default true
* @return bool true on success, false on error
* @throws \Exception
*/
public function setAllSeen($seen = true)
{
return q(
"UPDATE `notify` SET `seen` = %d WHERE `uid` = %d",
intval($seen),
intval(local_user())
);
return DBA::update('notify', ['seen' => $seen], ['uid' => local_user()]);
}
/**
* @brief List of pages for the Notifications TabBar
*
* @return array with with notifications TabBar data
* @throws \Exception
*/
public function getTabs()
{
@ -214,18 +186,18 @@ class NotificationsManager extends BaseObject
* @param array $notifs The array from the db query
* @param string $ident The notifications identifier (e.g. network)
* @return array
* string 'label' => The type of the notification
* string 'link' => URL to the source
* string 'image' => The avatar image
* string 'url' => The profile url of the contact
* string 'text' => The notification text
* string 'when' => The date of the notification
* string 'ago' => T relative date of the notification
* bool 'seen' => Is the notification marked as "seen"
* string 'label' => The type of the notification
* string 'link' => URL to the source
* string 'image' => The avatar image
* string 'url' => The profile url of the contact
* string 'text' => The notification text
* string 'when' => The date of the notification
* string 'ago' => T relative date of the notification
* bool 'seen' => Is the notification marked as "seen"
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private function formatNotifs(array $notifs, $ident = "")
{
$notif = [];
$arr = [];
if (DBA::isResult($notifs)) {
@ -358,7 +330,7 @@ class NotificationsManager extends BaseObject
break;
}
/// @todo Check if this part here is used at all
logger('Complete data: ' . json_encode($it) . ' - ' . System::callstack(20), LOGGER_DEBUG);
Logger::log('Complete data: ' . json_encode($it) . ' - ' . System::callstack(20), Logger::DEBUG);
$xmlhead = "<" . "?xml version='1.0' encoding='UTF-8' ?" . ">";
$obj = XML::parseString($xmlhead . $it['object']);
@ -399,14 +371,15 @@ class NotificationsManager extends BaseObject
/**
* @brief Get network notifications
*
* @param int|string $seen If 0 only include notifications into the query
* which aren't marked as "seen"
* @param int $start Start the query at this point
* @param int $limit Maximum number of query results
* @param int|string $seen If 0 only include notifications into the query
* which aren't marked as "seen"
* @param int $start Start the query at this point
* @param int $limit Maximum number of query results
*
* @return array with
* string 'ident' => Notification identifier
* array 'notifications' => Network notifications
* string 'ident' => Notification identifier
* array 'notifications' => Network notifications
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public function networkNotifs($seen = 0, $start = 0, $limit = 80)
{
@ -421,7 +394,7 @@ class NotificationsManager extends BaseObject
$fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar',
'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid'];
$params = ['order' => ['created' => true], 'limit' => [$start, $limit]];
$params = ['order' => ['received' => true], 'limit' => [$start, $limit]];
$items = Item::selectForUser(local_user(), $fields, $condition, $params);
@ -440,14 +413,15 @@ class NotificationsManager extends BaseObject
/**
* @brief Get system notifications
*
* @param int|string $seen If 0 only include notifications into the query
* which aren't marked as "seen"
* @param int $start Start the query at this point
* @param int $limit Maximum number of query results
* @param int|string $seen If 0 only include notifications into the query
* which aren't marked as "seen"
* @param int $start Start the query at this point
* @param int $limit Maximum number of query results
*
* @return array with
* string 'ident' => Notification identifier
* array 'notifications' => System notifications
* string 'ident' => Notification identifier
* array 'notifications' => System notifications
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public function systemNotifs($seen = 0, $start = 0, $limit = 80)
{
@ -455,20 +429,22 @@ class NotificationsManager extends BaseObject
$notifs = [];
$sql_seen = "";
$filter = ['uid' => local_user()];
if ($seen === 0) {
$sql_seen = " AND NOT `seen` ";
$filter['seen'] = false;
}
$r = q(
"SELECT `id`, `url`, `photo`, `msg`, `date`, `seen`, `verb` FROM `notify`
WHERE `uid` = %d $sql_seen ORDER BY `date` DESC LIMIT %d, %d ",
intval(local_user()),
intval($start),
intval($limit)
);
$params = [];
$params['order'] = ['date' => 'DESC'];
$params['limit'] = [$start, $limit];
if (DBA::isResult($r)) {
$notifs = $this->formatNotifs($r, $ident);
$stmtNotifies = DBA::select('notify',
['id', 'url', 'photo', 'msg', 'date', 'seen', 'verb'],
$filter,
$params);
if (DBA::isResult($stmtNotifies)) {
$notifs = $this->formatNotifs(DBA::toArray($stmtNotifies), $ident);
}
$arr = [
@ -482,14 +458,15 @@ class NotificationsManager extends BaseObject
/**
* @brief Get personal notifications
*
* @param int|string $seen If 0 only include notifications into the query
* which aren't marked as "seen"
* @param int $start Start the query at this point
* @param int $limit Maximum number of query results
* @param int|string $seen If 0 only include notifications into the query
* which aren't marked as "seen"
* @param int $start Start the query at this point
* @param int $limit Maximum number of query results
*
* @return array with
* string 'ident' => Notification identifier
* array 'notifications' => Personal notifications
* string 'ident' => Notification identifier
* array 'notifications' => Personal notifications
* @throws \Exception
*/
public function personalNotifs($seen = 0, $start = 0, $limit = 80)
{
@ -508,7 +485,7 @@ class NotificationsManager extends BaseObject
$fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar',
'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid'];
$params = ['order' => ['created' => true], 'limit' => [$start, $limit]];
$params = ['order' => ['received' => true], 'limit' => [$start, $limit]];
$items = Item::selectForUser(local_user(), $fields, $condition, $params);
@ -527,14 +504,15 @@ class NotificationsManager extends BaseObject
/**
* @brief Get home notifications
*
* @param int|string $seen If 0 only include notifications into the query
* which aren't marked as "seen"
* @param int $start Start the query at this point
* @param int $limit Maximum number of query results
* @param int|string $seen If 0 only include notifications into the query
* which aren't marked as "seen"
* @param int $start Start the query at this point
* @param int $limit Maximum number of query results
*
* @return array with
* string 'ident' => Notification identifier
* array 'notifications' => Home notifications
* string 'ident' => Notification identifier
* array 'notifications' => Home notifications
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public function homeNotifs($seen = 0, $start = 0, $limit = 80)
{
@ -549,7 +527,7 @@ class NotificationsManager extends BaseObject
$fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar',
'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid'];
$params = ['order' => ['created' => true], 'limit' => [$start, $limit]];
$params = ['order' => ['received' => true], 'limit' => [$start, $limit]];
$items = Item::selectForUser(local_user(), $fields, $condition, $params);
if (DBA::isResult($items)) {
@ -567,27 +545,36 @@ class NotificationsManager extends BaseObject
/**
* @brief Get introductions
*
* @param bool $all If false only include introductions into the query
* which aren't marked as ignored
* @param int $start Start the query at this point
* @param int $limit Maximum number of query results
* @param bool $all If false only include introductions into the query
* which aren't marked as ignored
* @param int $start Start the query at this point
* @param int $limit Maximum number of query results
* @param int $id When set, only the introduction with this id is displayed
*
* @return array with
* string 'ident' => Notification identifier
* array 'notifications' => Introductions
* string 'ident' => Notification identifier
* array 'notifications' => Introductions
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public function introNotifs($all = false, $start = 0, $limit = 80)
public function introNotifs($all = false, $start = 0, $limit = 80, $id = 0)
{
$ident = 'introductions';
$notifs = [];
$sql_extra = "";
if (!$all) {
$sql_extra = " AND `ignore` = 0 ";
if (empty($id)) {
if (!$all) {
$sql_extra = " AND NOT `ignore` ";
}
$sql_extra .= " AND NOT `intro`.`blocked` ";
} else {
$sql_extra = sprintf(" AND `intro`.`id` = %d ", intval($id));
}
/// @todo Fetch contact details by "Contact::getDetailsByUrl" instead of queries to contact, fcontact and gcontact
$r = q(
$stmtNotifies = DBA::p(
"SELECT `intro`.`id` AS `intro_id`, `intro`.*, `contact`.*,
`fcontact`.`name` AS `fname`, `fcontact`.`url` AS `furl`, `fcontact`.`addr` AS `faddr`,
`fcontact`.`photo` AS `fphoto`, `fcontact`.`request` AS `frequest`,
@ -598,14 +585,14 @@ class NotificationsManager extends BaseObject
LEFT JOIN `contact` ON `contact`.`id` = `intro`.`contact-id`
LEFT JOIN `gcontact` ON `gcontact`.`nurl` = `contact`.`nurl`
LEFT JOIN `fcontact` ON `intro`.`fid` = `fcontact`.`id`
WHERE `intro`.`uid` = %d $sql_extra AND `intro`.`blocked` = 0
LIMIT %d, %d",
intval($_SESSION['uid']),
intval($start),
intval($limit)
WHERE `intro`.`uid` = ? $sql_extra
LIMIT ?, ?",
$_SESSION['uid'],
$start,
$limit
);
if (DBA::isResult($r)) {
$notifs = $this->formatIntros($r);
if (DBA::isResult($stmtNotifies)) {
$notifs = $this->formatIntros(DBA::toArray($stmtNotifies));
}
$arr = [
@ -621,11 +608,15 @@ class NotificationsManager extends BaseObject
*
* @param array $intros The array from the db query
* @return array with the introductions
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
private function formatIntros($intros)
{
$knowyou = '';
$arr = [];
foreach ($intros as $it) {
// There are two kind of introduction. Contacts suggested by other contacts and normal connection requests.
// We have to distinguish between these two because they use different data.
@ -642,7 +633,7 @@ class NotificationsManager extends BaseObject
'madeby_zrl' => Contact::magicLink($it['url']),
'madeby_addr' => $it['addr'],
'contact_id' => $it['contact-id'],
'photo' => ((x($it, 'fphoto')) ? ProxyUtils::proxifyUrl($it['fphoto'], false, ProxyUtils::SIZE_SMALL) : "images/person-300.jpg"),
'photo' => (!empty($it['fphoto']) ? ProxyUtils::proxifyUrl($it['fphoto'], false, ProxyUtils::SIZE_SMALL) : "images/person-300.jpg"),
'name' => $it['fname'],
'url' => $it['furl'],
'zrl' => Contact::magicLink($it['furl']),
@ -674,7 +665,7 @@ class NotificationsManager extends BaseObject
'uid' => $_SESSION['uid'],
'intro_id' => $it['intro_id'],
'contact_id' => $it['contact-id'],
'photo' => ((x($it, 'photo')) ? ProxyUtils::proxifyUrl($it['photo'], false, ProxyUtils::SIZE_SMALL) : "images/person-300.jpg"),
'photo' => (!empty($it['photo']) ? ProxyUtils::proxifyUrl($it['photo'], false, ProxyUtils::SIZE_SMALL) : "images/person-300.jpg"),
'name' => $it['name'],
'location' => BBCode::convert($it['glocation'], false),
'about' => BBCode::convert($it['gabout'], false),
@ -704,6 +695,7 @@ class NotificationsManager extends BaseObject
* @param array $arr The input array with the intro data
*
* @return array The array with the intro data
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private function getMissingIntroData($arr)
{

Some files were not shown because too many files have changed in this diff Show more