Merge remote-tracking branch 'upstream/2019.03-RC' into worker2

This commit is contained in:
Michael 2019-02-27 06:41:14 +00:00
commit fb371e1048
178 changed files with 18279 additions and 15676 deletions

View file

@ -1 +1 @@
2019.03-dev 2019.03-rc

View file

@ -32,10 +32,7 @@
* *
*/ */
use Friendica\App;
use Friendica\Core\Config;
use Friendica\Factory; use Friendica\Factory;
use Friendica\Util\BasePath;
use Friendica\Util\ExAuth; use Friendica\Util\ExAuth;
if (sizeof($_SERVER["argv"]) == 0) { if (sizeof($_SERVER["argv"]) == 0) {
@ -54,12 +51,7 @@ chdir($directory);
require dirname(__DIR__) . '/vendor/autoload.php'; require dirname(__DIR__) . '/vendor/autoload.php';
$basedir = BasePath::create(dirname(__DIR__), $_SERVER); $a = Factory\DependencyFactory::setUp('auth_ejabbered', dirname(__DIR__));
$configLoader = new Config\ConfigCacheLoader($basedir);
$config = Factory\ConfigFactory::createCache($configLoader);
$logger = Factory\LoggerFactory::create('auth_ejabberd', $config);
$a = new App($config, $logger);
if ($a->getMode()->isNormal()) { if ($a->getMode()->isNormal()) {
$oAuth = new ExAuth(); $oAuth = new ExAuth();

View file

@ -3,16 +3,9 @@
require dirname(__DIR__) . '/vendor/autoload.php'; require dirname(__DIR__) . '/vendor/autoload.php';
use Friendica\Core\Config;
use Friendica\Factory; use Friendica\Factory;
use Friendica\Util\BasePath;
$basedir = BasePath::create(dirname(__DIR__), $_SERVER); $a = Factory\DependencyFactory::setUp('console', dirname(__DIR__));
$configLoader = new Config\ConfigCacheLoader($basedir);
$config = Factory\ConfigFactory::createCache($configLoader);
$logger = Factory\LoggerFactory::create('console', $config);
$a = new Friendica\App($config, $logger);
\Friendica\BaseObject::setApp($a); \Friendica\BaseObject::setApp($a);
(new Friendica\Core\Console($argv))->execute(); (new Friendica\Core\Console($argv))->execute();

View file

@ -7,12 +7,11 @@
* This script was taken from http://php.net/manual/en/function.pcntl-fork.php * This script was taken from http://php.net/manual/en/function.pcntl-fork.php
*/ */
use Friendica\App;
use Friendica\Core\Config; use Friendica\Core\Config;
use Friendica\Core\Logger;
use Friendica\Core\Worker; use Friendica\Core\Worker;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\Factory; use Friendica\Factory;
use Friendica\Util\BasePath;
// Get options // Get options
$shortopts = 'f'; $shortopts = 'f';
@ -33,12 +32,7 @@ if (!file_exists("boot.php") && (sizeof($_SERVER["argv"]) != 0)) {
require dirname(__DIR__) . '/vendor/autoload.php'; require dirname(__DIR__) . '/vendor/autoload.php';
$basedir = BasePath::create(dirname(__DIR__), $_SERVER); $a = Factory\DependencyFactory::setUp('daemon', dirname(__DIR__));
$configLoader = new Config\ConfigCacheLoader($basedir);
$config = Factory\ConfigFactory::createCache($configLoader);
$logger = Factory\LoggerFactory::create('daemon', $config);
$a = new App($config, $logger);
if ($a->getMode()->isInstall()) { if ($a->getMode()->isInstall()) {
die("Friendica isn't properly installed yet.\n"); die("Friendica isn't properly installed yet.\n");
@ -108,7 +102,7 @@ if ($mode == "stop") {
unlink($pidfile); unlink($pidfile);
$logger->notice("Worker daemon process was killed", ["pid" => $pid]); Logger::notice("Worker daemon process was killed", ["pid" => $pid]);
Config::set('system', 'worker_daemon_mode', false); Config::set('system', 'worker_daemon_mode', false);
die("Worker daemon process $pid was killed.\n"); die("Worker daemon process $pid was killed.\n");
@ -118,7 +112,7 @@ if (!empty($pid) && posix_kill($pid, 0)) {
die("Daemon process $pid is already running.\n"); die("Daemon process $pid is already running.\n");
} }
$logger->notice('Starting worker daemon.', ["pid" => $pid]); Logger::notice('Starting worker daemon.', ["pid" => $pid]);
if (!$foreground) { if (!$foreground) {
echo "Starting worker daemon.\n"; echo "Starting worker daemon.\n";
@ -150,7 +144,9 @@ if (!$foreground) {
file_put_contents($pidfile, $pid); file_put_contents($pidfile, $pid);
// We lose the database connection upon forking // We lose the database connection upon forking
$a->loadDatabase(); /// @todo refactoring during https://github.com/friendica/friendica/issues/6720
$basePath = \Friendica\Util\BasePath::create(dirname(__DIR__), $_SERVER);
Factory\DBFactory::init($basePath, $a->getConfigCache(), $a->getProfiler(), $_SERVER);
} }
Config::set('system', 'worker_daemon_mode', true); Config::set('system', 'worker_daemon_mode', true);
@ -166,7 +162,7 @@ $last_cron = 0;
// Now running as a daemon. // Now running as a daemon.
while (true) { while (true) {
if (!$do_cron && ($last_cron + $wait_interval) < time()) { if (!$do_cron && ($last_cron + $wait_interval) < time()) {
$logger->info('Forcing cron worker call.', ["pid" => $pid]); Logger::info('Forcing cron worker call.', ["pid" => $pid]);
$do_cron = true; $do_cron = true;
} }
@ -180,7 +176,7 @@ while (true) {
$last_cron = time(); $last_cron = time();
} }
$logger->info("Sleeping", ["pid" => $pid]); Logger::info("Sleeping", ["pid" => $pid]);
$start = time(); $start = time();
do { do {
$seconds = (time() - $start); $seconds = (time() - $start);
@ -197,10 +193,10 @@ while (true) {
if ($timeout) { if ($timeout) {
$do_cron = true; $do_cron = true;
$logger->info("Woke up after $wait_interval seconds.", ["pid" => $pid, 'sleep' => $wait_interval]); Logger::info("Woke up after $wait_interval seconds.", ["pid" => $pid, 'sleep' => $wait_interval]);
} else { } else {
$do_cron = false; $do_cron = false;
$logger->info("Worker jobs are calling to be forked.", ["pid" => $pid]); Logger::info("Worker jobs are calling to be forked.", ["pid" => $pid]);
} }
} }

View file

@ -10,7 +10,6 @@ use Friendica\Core\Config;
use Friendica\Core\Update; use Friendica\Core\Update;
use Friendica\Core\Worker; use Friendica\Core\Worker;
use Friendica\Factory; use Friendica\Factory;
use Friendica\Util\BasePath;
// Get options // Get options
$shortopts = 'sn'; $shortopts = 'sn';
@ -31,12 +30,7 @@ if (!file_exists("boot.php") && (sizeof($_SERVER["argv"]) != 0)) {
require dirname(__DIR__) . '/vendor/autoload.php'; require dirname(__DIR__) . '/vendor/autoload.php';
$basedir = BasePath::create(dirname(__DIR__), $_SERVER); $a = Factory\DependencyFactory::setUp('worker', dirname(__DIR__));
$configLoader = new Config\ConfigCacheLoader($basedir);
$config = Factory\ConfigFactory::createCache($configLoader);
$logger = Factory\LoggerFactory::create('worker', $config);
$a = new App($config, $logger);
// Check the database structure and possibly fixes it // Check the database structure and possibly fixes it
Update::check($a->getBasePath(), true); Update::check($a->getBasePath(), true);

View file

@ -25,12 +25,13 @@ use Friendica\Core\Protocol;
use Friendica\Core\System; use Friendica\Core\System;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Friendica\Model\Term;
use Friendica\Util\BasePath; use Friendica\Util\BasePath;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
define('FRIENDICA_PLATFORM', 'Friendica'); define('FRIENDICA_PLATFORM', 'Friendica');
define('FRIENDICA_CODENAME', 'The Tazmans Flax-lily'); define('FRIENDICA_CODENAME', 'The Tazmans Flax-lily');
define('FRIENDICA_VERSION', '2019.03-dev'); define('FRIENDICA_VERSION', '2019.03-rc');
define('DFRN_PROTOCOL_VERSION', '2.23'); define('DFRN_PROTOCOL_VERSION', '2.23');
define('NEW_UPDATE_ROUTINE_VERSION', 1170); define('NEW_UPDATE_ROUTINE_VERSION', 1170);
@ -171,23 +172,27 @@ define('NOTIFY_SYSTEM', 32768);
/* @}*/ /* @}*/
/** /** @deprecated since 2019.03, use Term::UNKNOWN instead */
* @name Term define('TERM_UNKNOWN', Term::UNKNOWN);
* /** @deprecated since 2019.03, use Term::HASHTAG instead */
* Tag/term types define('TERM_HASHTAG', Term::HASHTAG);
* @{ /** @deprecated since 2019.03, use Term::MENTION instead */
*/ define('TERM_MENTION', Term::MENTION);
define('TERM_UNKNOWN', 0); /** @deprecated since 2019.03, use Term::CATEGORY instead */
define('TERM_HASHTAG', 1); define('TERM_CATEGORY', Term::CATEGORY);
define('TERM_MENTION', 2); /** @deprecated since 2019.03, use Term::PCATEGORY instead */
define('TERM_CATEGORY', 3); define('TERM_PCATEGORY', Term::PCATEGORY);
define('TERM_PCATEGORY', 4); /** @deprecated since 2019.03, use Term::FILE instead */
define('TERM_FILE', 5); define('TERM_FILE', Term::FILE);
define('TERM_SAVEDSEARCH', 6); /** @deprecated since 2019.03, use Term::SAVEDSEARCH instead */
define('TERM_CONVERSATION', 7); define('TERM_SAVEDSEARCH', Term::SAVEDSEARCH);
/** @deprecated since 2019.03, use Term::CONVERSATION instead */
define('TERM_CONVERSATION', Term::CONVERSATION);
define('TERM_OBJ_POST', 1); /** @deprecated since 2019.03, use Term::OBJECT_TYPE_POST instead */
define('TERM_OBJ_PHOTO', 2); define('TERM_OBJ_POST', Term::OBJECT_TYPE_POST);
/** @deprecated since 2019.03, use Term::OBJECT_TYPE_PHOTO instead */
define('TERM_OBJ_PHOTO', Term::OBJECT_TYPE_PHOTO);
/** /**
* @name Namespaces * @name Namespaces

View file

@ -34,23 +34,25 @@
"lightopenid/lightopenid": "dev-master", "lightopenid/lightopenid": "dev-master",
"michelf/php-markdown": "^1.7", "michelf/php-markdown": "^1.7",
"mobiledetect/mobiledetectlib": "2.8.*", "mobiledetect/mobiledetectlib": "2.8.*",
"monolog/monolog": "^1.24",
"paragonie/random_compat": "^2.0", "paragonie/random_compat": "^2.0",
"pear/Text_LanguageDetect": "1.*", "pear/text_languagedetect": "1.*",
"psr/container": "^1.0",
"seld/cli-prompt": "^1.0", "seld/cli-prompt": "^1.0",
"smarty/smarty": "^3.1", "smarty/smarty": "^3.1",
"fxp/composer-asset-plugin": "~1.3", "fxp/composer-asset-plugin": "~1.3",
"bower-asset/base64": "^1.0", "bower-asset/base64": "^1.0",
"bower-asset/Chart-js": "^2.7", "bower-asset/chart-js": "^2.7",
"bower-asset/perfect-scrollbar": "^0.6", "bower-asset/perfect-scrollbar": "^0.6",
"bower-asset/vue": "^2.5", "bower-asset/vue": "^2.5",
"npm-asset/jquery": "^2.0", "npm-asset/jquery": "^2.0",
"npm-asset/jquery-colorbox": "^1.6", "npm-asset/jquery-colorbox": "^1.6",
"npm-asset/jquery-datetimepicker": "^2.4.0", "npm-asset/jquery-datetimepicker": "^2.4.0",
"npm-asset/jgrowl": "^1.4", "npm-asset/jgrowl": "^1.4",
"npm-asset/moment": "^2.20.1",
"npm-asset/fullcalendar": "^3.0.1", "npm-asset/fullcalendar": "^3.0.1",
"npm-asset/cropperjs": "1.2.2", "npm-asset/cropperjs": "1.2.2",
"npm-asset/imagesloaded": "4.1.4", "npm-asset/imagesloaded": "4.1.4"
"monolog/monolog": "^1.24"
}, },
"repositories": [ "repositories": [
{ {
@ -96,7 +98,7 @@
"phpunit/dbunit": "^2.0", "phpunit/dbunit": "^2.0",
"phpdocumentor/reflection-docblock": "^3.0.2", "phpdocumentor/reflection-docblock": "^3.0.2",
"phpunit/php-token-stream": "^1.4.2", "phpunit/php-token-stream": "^1.4.2",
"mikey179/vfsStream": "^1.6", "mikey179/vfsstream": "^1.6",
"mockery/mockery": "^1.2", "mockery/mockery": "^1.2",
"johnkary/phpunit-speedtrap": "1.1" "johnkary/phpunit-speedtrap": "1.1"
}, },

87
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "b944adfb6ede89430ba3b7e9ebb45acb", "content-hash": "8897c1f6912cc9b889534a8c59deead1",
"packages": [ "packages": [
{ {
"name": "asika/simple-console", "name": "asika/simple-console",
@ -1026,32 +1026,18 @@
}, },
{ {
"name": "npm-asset/fullcalendar", "name": "npm-asset/fullcalendar",
"version": "3.9.0", "version": "3.10.0",
"dist": { "dist": {
"type": "tar", "type": "tar",
"url": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-3.9.0.tgz", "url": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-3.10.0.tgz",
"reference": null, "reference": null,
"shasum": "b608a9989f3416f0b1d526c6bdfeeaf2ac79eda5" "shasum": "cc5e87d518fd6550e142816a31dd191664847919"
},
"require": {
"npm-asset/jquery": ">=2,<4.0",
"npm-asset/moment": ">=2.20.1,<3.0.0"
}, },
"type": "npm-asset-library", "type": "npm-asset-library",
"extra": { "extra": {
"npm-asset-bugs": { "npm-asset-bugs": {
"url": "https://fullcalendar.io/wiki/Reporting-Bugs/" "url": "https://fullcalendar.io/wiki/Reporting-Bugs/"
}, },
"npm-asset-files": [
"dist/*.js",
"dist/*.css",
"dist/*.d.ts",
"dist/locale/*.js",
"README.*",
"LICENSE.*",
"CHANGELOG.*",
"CONTRIBUTING.*"
],
"npm-asset-main": "dist/fullcalendar.js", "npm-asset-main": "dist/fullcalendar.js",
"npm-asset-directories": [], "npm-asset-directories": [],
"npm-asset-repository": { "npm-asset-repository": {
@ -1061,7 +1047,7 @@
"npm-asset-scripts": { "npm-asset-scripts": {
"clean": "gulp clean", "clean": "gulp clean",
"dist": "gulp dist", "dist": "gulp dist",
"lint": "gulp lint", "lint": "gulp lint-and-example-repos",
"test": "gulp test:single" "test": "gulp test:single"
} }
}, },
@ -1083,7 +1069,7 @@
"full-sized", "full-sized",
"jquery-plugin" "jquery-plugin"
], ],
"time": "2018-03-05T03:30:23+00:00" "time": "2019-01-11T02:39:12+00:00"
}, },
{ {
"name": "npm-asset/imagesloaded", "name": "npm-asset/imagesloaded",
@ -1882,6 +1868,55 @@
], ],
"time": "2016-08-06T20:24:11+00:00" "time": "2016-08-06T20:24:11+00:00"
}, },
{
"name": "psr/container",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/container.git",
"reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
"reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Container\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common Container Interface (PHP FIG PSR-11)",
"homepage": "https://github.com/php-fig/container",
"keywords": [
"PSR-11",
"container",
"container-interface",
"container-interop",
"psr"
],
"time": "2017-02-14T16:28:37+00:00"
},
{ {
"name": "psr/http-message", "name": "psr/http-message",
"version": "1.0.1", "version": "1.0.1",
@ -2650,6 +2685,7 @@
"testing", "testing",
"xunit" "xunit"
], ],
"abandoned": true,
"time": "2016-12-02T14:39:14+00:00" "time": "2016-12-02T14:39:14+00:00"
}, },
{ {
@ -3040,6 +3076,7 @@
"mock", "mock",
"xunit" "xunit"
], ],
"abandoned": true,
"time": "2017-06-30T09:13:00+00:00" "time": "2017-06-30T09:13:00+00:00"
}, },
{ {
@ -3143,7 +3180,7 @@
} }
], ],
"description": "Provides the functionality to compare PHP values for equality", "description": "Provides the functionality to compare PHP values for equality",
"homepage": "https://github.com/sebastianbergmann/comparator", "homepage": "http://www.github.com/sebastianbergmann/comparator",
"keywords": [ "keywords": [
"comparator", "comparator",
"compare", "compare",
@ -3245,7 +3282,7 @@
} }
], ],
"description": "Provides functionality to handle HHVM/PHP environments", "description": "Provides functionality to handle HHVM/PHP environments",
"homepage": "https://github.com/sebastianbergmann/environment", "homepage": "http://www.github.com/sebastianbergmann/environment",
"keywords": [ "keywords": [
"Xdebug", "Xdebug",
"environment", "environment",
@ -3313,7 +3350,7 @@
} }
], ],
"description": "Provides the functionality to export PHP variables for visualization", "description": "Provides the functionality to export PHP variables for visualization",
"homepage": "https://github.com/sebastianbergmann/exporter", "homepage": "http://www.github.com/sebastianbergmann/exporter",
"keywords": [ "keywords": [
"export", "export",
"exporter" "exporter"
@ -3365,7 +3402,7 @@
} }
], ],
"description": "Snapshotting of global state", "description": "Snapshotting of global state",
"homepage": "https://github.com/sebastianbergmann/global-state", "homepage": "http://www.github.com/sebastianbergmann/global-state",
"keywords": [ "keywords": [
"global state" "global state"
], ],
@ -3467,7 +3504,7 @@
} }
], ],
"description": "Provides functionality to recursively process PHP variables", "description": "Provides functionality to recursively process PHP variables",
"homepage": "https://github.com/sebastianbergmann/recursion-context", "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
"time": "2016-11-19T07:33:16+00:00" "time": "2016-11-19T07:33:16+00:00"
}, },
{ {

View file

@ -135,9 +135,14 @@ return [
// Disables the check if a mail address is in a valid format and can be resolved via DNS. // Disables the check if a mail address is in a valid format and can be resolved via DNS.
'disable_email_validation' => false, 'disable_email_validation' => false,
// disable_mentions_removal (Boolean) // disable_implicit_mentions (Boolean) since 2019.03
// Disables the automatic removal of implicit mentions in ActivityPub postings. // Implicit mentions are mentions in the body of replies that are redundant in a thread-enabled system like Friendica.
'disable_mentions_removal' => false, // This config key disables the gathering of implicit mentions in incoming and outgoing posts.
// Also disables the default automatic removal of implicit mentions from the body of incoming posts.
// Also disables the default automatic addition of implicit mentions in the body of outgoing posts.
// Disabling implicit mentions also affects the "explicit_mentions" additional feature by limiting it
// to the replied-to post author mention in the comment boxes.
'disable_implicit_mentions' => false,
// disable_url_validation (Boolean) // disable_url_validation (Boolean)
// Disables the DNS lookup of an URL. // Disables the DNS lookup of an URL.

View file

@ -18,10 +18,8 @@ If you do not have a Friendica account yet, you can register a temporary one at
The account will expire after 7 days, but you can ask the server admin to keep your account longer, should the problem not be resolved after that. The account will expire after 7 days, but you can ask the server admin to keep your account longer, should the problem not be resolved after that.
Before you begin: Choose a domain name or subdomain name for your server. Before you begin: Choose a domain name or subdomain name for your server.
Put some thought into this. Changing it after installation is currently not supported. Put some thought into this.
Things will break, and some of your friends may have difficulty communicating with you. While changing it after installation is supported, things still might break.
We plan to address this limitation in a future release.
Requirements Requirements
--- ---
@ -57,7 +55,7 @@ The Linux commands to clone the repository into a directory "mywebsite" would be
git clone https://github.com/friendica/friendica.git -b master mywebsite git clone https://github.com/friendica/friendica.git -b master mywebsite
cd mywebsite cd mywebsite
bin/composer.phar install bin/composer.phar install --no-dev
Make sure the folder *view/smarty3* exists and is writable by the webserver user, in this case `www-data` Make sure the folder *view/smarty3* exists and is writable by the webserver user, in this case `www-data`

View file

@ -15,6 +15,6 @@ Remember the link at the top of this page will bring you back here.
Once you've added some groups, <a href="help/Quick-Start-andfinally">move on to the next section</a>. Once you've added some groups, <a href="help/Quick-Start-andfinally">move on to the next section</a>.
<iframe src="http://dir.friendica.social/directory" width="950" height="600"></iframe> <iframe src="https://dir.friendica.social/forum" width="950" height="600"></iframe>

View file

@ -21,7 +21,7 @@ You can get the latest changes at any time with
cd path/to/friendica cd path/to/friendica
git pull git pull
bin/composer.phar install bin/composer.phar install --no-dev
The addon tree has to be updated separately like so: The addon tree has to be updated separately like so:

View file

@ -21,6 +21,6 @@ Solltest Du beim Stöbern durch die vielen Gruppen nicht wieder hierher zurück
Wenn Du einige Gruppen hinzugefügt hast, gehe <a href="help/Quick-Start-andfinally">weiter zum nächsten Schritt</a>. Wenn Du einige Gruppen hinzugefügt hast, gehe <a href="help/Quick-Start-andfinally">weiter zum nächsten Schritt</a>.
<iframe src="https://dir.friendica.social/home" width="950" height="600"></iframe> <iframe src="https://dir.friendica.social/forum" width="950" height="600"></iframe>

View file

@ -326,69 +326,7 @@ function api_call(App $a)
Logger::info(API_LOG_PREFIX . 'username {username}', ['module' => 'api', 'action' => 'call', 'username' => $a->user['username'], 'duration' => round($duration, 2)]); Logger::info(API_LOG_PREFIX . 'username {username}', ['module' => 'api', 'action' => 'call', 'username' => $a->user['username'], 'duration' => round($duration, 2)]);
if (Config::get("system", "profiler")) { $a->getProfiler()->saveLog($a->getLogger(), API_LOG_PREFIX . 'performance');
$duration = microtime(true)-$a->performance["start"];
/// @TODO round() really everywhere?
Logger::debug(
API_LOG_PREFIX . 'performance',
[
'module' => 'api',
'action' => 'call',
'database_read' => round($a->performance["database"] - $a->performance["database_write"], 3),
'database_write' => round($a->performance["database_write"], 3),
'cache_read' => round($a->performance["cache"], 3),
'cache_write' => round($a->performance["cache_write"], 3),
'network_io' => round($a->performance["network"], 2),
'file_io' => round($a->performance["file"], 2),
'other_io' => round($duration - ($a->performance["database"]
+ $a->performance["cache"] + $a->performance["cache_write"]
+ $a->performance["network"] + $a->performance["file"]), 2),
'total' => round($duration, 2)
]
);
if (Config::get("rendertime", "callstack")) {
$o = "Database Read:\n";
foreach ($a->callstack["database"] as $func => $time) {
$time = round($time, 3);
if ($time > 0) {
$o .= $func . ": " . $time . "\n";
}
}
$o .= "\nDatabase Write:\n";
foreach ($a->callstack["database_write"] as $func => $time) {
$time = round($time, 3);
if ($time > 0) {
$o .= $func . ": " . $time . "\n";
}
}
$o = "Cache Read:\n";
foreach ($a->callstack["cache"] as $func => $time) {
$time = round($time, 3);
if ($time > 0) {
$o .= $func . ": " . $time . "\n";
}
}
$o .= "\nCache Write:\n";
foreach ($a->callstack["cache_write"] as $func => $time) {
$time = round($time, 3);
if ($time > 0) {
$o .= $func . ": " . $time . "\n";
}
}
$o .= "\nNetwork:\n";
foreach ($a->callstack["network"] as $func => $time) {
$time = round($time, 3);
if ($time > 0) {
$o .= $func . ": " . $time . "\n";
}
}
Logger::debug(API_LOG_PREFIX . $o, ['module' => 'api', 'action' => 'call']);
}
}
if (false === $return) { if (false === $return) {
/* /*
@ -1590,7 +1528,9 @@ function api_search($type)
if (api_user() === false || $user_info === false) { throw new ForbiddenException(); } if (api_user() === false || $user_info === false) { throw new ForbiddenException(); }
if (empty($_REQUEST['q'])) { throw new BadRequestException('q parameter is required.'); } if (empty($_REQUEST['q'])) {
throw new BadRequestException('q parameter is required.');
}
$searchTerm = trim(rawurldecode($_REQUEST['q'])); $searchTerm = trim(rawurldecode($_REQUEST['q']));
@ -4461,6 +4401,7 @@ function api_fr_photo_delete($type)
if (api_user() === false) { if (api_user() === false) {
throw new ForbiddenException(); throw new ForbiddenException();
} }
// input params // input params
$photo_id = defaults($_REQUEST, 'photo_id', null); $photo_id = defaults($_REQUEST, 'photo_id', null);
@ -4469,11 +4410,12 @@ function api_fr_photo_delete($type)
if ($photo_id == null) { if ($photo_id == null) {
throw new BadRequestException("no photo_id specified"); throw new BadRequestException("no photo_id specified");
} }
// check if photo is existing in database // check if photo is existing in database
$r = Photo::exists(['resource-id' => $photo_id, 'uid' => api_user()]); if (!Photo::exists(['resource-id' => $photo_id, 'uid' => api_user()])) {
if (!$r) {
throw new BadRequestException("photo not available"); throw new BadRequestException("photo not available");
} }
// now we can perform on the deletion of the photo // now we can perform on the deletion of the photo
$result = Photo::delete(['uid' => api_user(), 'resource-id' => $photo_id]); $result = Photo::delete(['uid' => api_user(), 'resource-id' => $photo_id]);

View file

@ -365,7 +365,7 @@ function localize_item(&$item)
// Only create a redirection to a magic link when logged in // Only create a redirection to a magic link when logged in
if (!empty($item['plink']) && (local_user() || remote_user())) { if (!empty($item['plink']) && (local_user() || remote_user())) {
$item['plink'] = Contact::magicLinkbyContact($author, $item['plink']); $item['plink'] = Contact::magicLinkByContact($author, $item['plink']);
} }
} }
@ -625,7 +625,7 @@ function conversation(App $a, array $items, Pager $pager, $mode, $update, $previ
$author = ['uid' => 0, 'id' => $item['author-id'], $author = ['uid' => 0, 'id' => $item['author-id'],
'network' => $item['author-network'], 'url' => $item['author-link']]; 'network' => $item['author-network'], 'url' => $item['author-link']];
$profile_link = Contact::magicLinkbyContact($author); $profile_link = Contact::magicLinkByContact($author);
if (strpos($profile_link, 'redir/') === 0) { if (strpos($profile_link, 'redir/') === 0) {
$sparkle = ' sparkle'; $sparkle = ' sparkle';
@ -660,21 +660,12 @@ function conversation(App $a, array $items, Pager $pager, $mode, $update, $previ
list($categories, $folders) = get_cats_and_terms($item); list($categories, $folders) = get_cats_and_terms($item);
$profile_name_e = $profile_name;
if (!empty($item['content-warning']) && PConfig::get(local_user(), 'system', 'disable_cw', false)) { if (!empty($item['content-warning']) && PConfig::get(local_user(), 'system', 'disable_cw', false)) {
$title_e = ucfirst($item['content-warning']); $title = ucfirst($item['content-warning']);
} else { } else {
$title_e = $item['title']; $title = $item['title'];
} }
$body_e = $body;
$tags_e = $tags['tags'];
$hashtags_e = $tags['hashtags'];
$mentions_e = $tags['mentions'];
$location_e = $location;
$owner_name_e = $owner_name;
$tmp_item = [ $tmp_item = [
'template' => $tpl, 'template' => $tpl,
'id' => ($preview ? 'P0' : $item['id']), 'id' => ($preview ? 'P0' : $item['id']),
@ -684,27 +675,28 @@ function conversation(App $a, array $items, Pager $pager, $mode, $update, $previ
'linktitle' => L10n::t('View %s\'s profile @ %s', $profile_name, $item['author-link']), 'linktitle' => L10n::t('View %s\'s profile @ %s', $profile_name, $item['author-link']),
'profile_url' => $profile_link, 'profile_url' => $profile_link,
'item_photo_menu' => item_photo_menu($item), 'item_photo_menu' => item_photo_menu($item),
'name' => $profile_name_e, 'name' => $profile_name,
'sparkle' => $sparkle, 'sparkle' => $sparkle,
'lock' => $lock, 'lock' => $lock,
'thumb' => System::removedBaseUrl(ProxyUtils::proxifyUrl($item['author-avatar'], false, ProxyUtils::SIZE_THUMB)), 'thumb' => System::removedBaseUrl(ProxyUtils::proxifyUrl($item['author-avatar'], false, ProxyUtils::SIZE_THUMB)),
'title' => $title_e, 'title' => $title,
'body' => $body_e, 'body' => $body,
'tags' => $tags_e, 'tags' => $tags['tags'],
'hashtags' => $hashtags_e, 'hashtags' => $tags['hashtags'],
'mentions' => $mentions_e, 'mentions' => $tags['mentions'],
'implicit_mentions' => $tags['implicit_mentions'],
'txt_cats' => L10n::t('Categories:'), 'txt_cats' => L10n::t('Categories:'),
'txt_folders' => L10n::t('Filed under:'), 'txt_folders' => L10n::t('Filed under:'),
'has_cats' => ((count($categories)) ? 'true' : ''), 'has_cats' => ((count($categories)) ? 'true' : ''),
'has_folders' => ((count($folders)) ? 'true' : ''), 'has_folders' => ((count($folders)) ? 'true' : ''),
'categories' => $categories, 'categories' => $categories,
'folders' => $folders, 'folders' => $folders,
'text' => strip_tags($body_e), 'text' => strip_tags($body),
'localtime' => DateTimeFormat::local($item['created'], 'r'), 'localtime' => DateTimeFormat::local($item['created'], 'r'),
'ago' => (($item['app']) ? L10n::t('%s from %s', Temporal::getRelativeDate($item['created']),$item['app']) : Temporal::getRelativeDate($item['created'])), 'ago' => (($item['app']) ? L10n::t('%s from %s', Temporal::getRelativeDate($item['created']),$item['app']) : Temporal::getRelativeDate($item['created'])),
'location' => $location_e, 'location' => $location,
'indent' => '', 'indent' => '',
'owner_name' => $owner_name_e, 'owner_name' => $owner_name,
'owner_url' => $owner_url, 'owner_url' => $owner_url,
'owner_photo' => System::removedBaseUrl(ProxyUtils::proxifyUrl($item['owner-avatar'], false, ProxyUtils::SIZE_THUMB)), 'owner_photo' => System::removedBaseUrl(ProxyUtils::proxifyUrl($item['owner-avatar'], false, ProxyUtils::SIZE_THUMB)),
'plink' => Item::getPlink($item), 'plink' => Item::getPlink($item),
@ -858,7 +850,7 @@ function item_photo_menu($item) {
$author = ['uid' => 0, 'id' => $item['author-id'], $author = ['uid' => 0, 'id' => $item['author-id'],
'network' => $item['author-network'], 'url' => $item['author-link']]; 'network' => $item['author-network'], 'url' => $item['author-link']];
$profile_link = Contact::magicLinkbyContact($author); $profile_link = Contact::magicLinkByContact($author, $item['author-link']);
$sparkle = (strpos($profile_link, 'redir/') === 0); $sparkle = (strpos($profile_link, 'redir/') === 0);
$cid = 0; $cid = 0;
@ -873,9 +865,9 @@ function item_photo_menu($item) {
} }
if ($sparkle) { if ($sparkle) {
$status_link = $profile_link . '?url=status'; $status_link = $profile_link . '?tab=status';
$photos_link = $profile_link . '?url=photos'; $photos_link = str_replace('/profile/', '/photos/', $profile_link);
$profile_link = $profile_link . '?url=profile'; $profile_link = $profile_link . '?=profile';
} }
if ($cid && !$item['self']) { if ($cid && !$item['self']) {
@ -966,7 +958,7 @@ function builtin_activity_puller($item, &$conv_responses) {
if (activity_match($item['verb'], $verb) && ($item['id'] != $item['parent'])) { if (activity_match($item['verb'], $verb) && ($item['id'] != $item['parent'])) {
$author = ['uid' => 0, 'id' => $item['author-id'], $author = ['uid' => 0, 'id' => $item['author-id'],
'network' => $item['author-network'], 'url' => $item['author-link']]; 'network' => $item['author-network'], 'url' => $item['author-link']];
$url = Contact::magicLinkbyContact($author); $url = Contact::magicLinkByContact($author);
if (strpos($url, 'redir/') === 0) { if (strpos($url, 'redir/') === 0) {
$sparkle = ' class="sparkle" '; $sparkle = ' class="sparkle" ';
} }
@ -1082,10 +1074,9 @@ function format_like($cnt, array $arr, $type, $id) {
break; break;
} }
$expanded .= "\t" . '<div class="wall-item-' . $type . '-expanded" id="' . $type . 'list-' . $id . '" style="display: none;" >' . $explikers . EOL . '</div>'; $expanded .= "\t" . '<p class="wall-item-' . $type . '-expanded" id="' . $type . 'list-' . $id . '" style="display: none;" >' . $explikers . EOL . '</p>';
} }
$phrase .= EOL;
$o .= Renderer::replaceMacros(Renderer::getMarkupTemplate('voting_fakelink.tpl'), [ $o .= Renderer::replaceMacros(Renderer::getMarkupTemplate('voting_fakelink.tpl'), [
'$phrase' => $phrase, '$phrase' => $phrase,
'$type' => $type, '$type' => $type,

View file

@ -33,8 +33,8 @@ function notification($params)
$a = \get_app(); $a = \get_app();
// Temporary logging for finding the origin // Temporary logging for finding the origin
if (!isset($params['language']) || !isset($params['uid'])) { if (!isset($params['uid'])) {
Logger::log('Missing parameters.' . System::callstack()); Logger::notice('Missing parameters "uid".', ['params' => $params, 'callstack' => System::callstack()]);
} }
// Ensure that the important fields are set at any time // Ensure that the important fields are set at any time
@ -42,7 +42,7 @@ function notification($params)
$user = DBA::selectFirst('user', $fields, ['uid' => $params['uid']]); $user = DBA::selectFirst('user', $fields, ['uid' => $params['uid']]);
if (!DBA::isResult($user)) { if (!DBA::isResult($user)) {
Logger::log('Unknown user ' . $params['uid']); Logger::error('Unknown user', ['uid' => $params['uid']]);
return false; return false;
} }

View file

@ -4,10 +4,7 @@
* Friendica * Friendica
*/ */
use Friendica\App;
use Friendica\Core\Config;
use Friendica\Factory; use Friendica\Factory;
use Friendica\Util\BasePath;
if (!file_exists(__DIR__ . '/vendor/autoload.php')) { if (!file_exists(__DIR__ . '/vendor/autoload.php')) {
die('Vendor path not found. Please execute "bin/composer.phar --no-dev install" on the command line in the web root.'); die('Vendor path not found. Please execute "bin/composer.phar --no-dev install" on the command line in the web root.');
@ -15,13 +12,7 @@ if (!file_exists(__DIR__ . '/vendor/autoload.php')) {
require __DIR__ . '/vendor/autoload.php'; require __DIR__ . '/vendor/autoload.php';
$basedir = BasePath::create(__DIR__, $_SERVER); $a = Factory\DependencyFactory::setUp('index', __DIR__, false);
$configLoader = new Config\ConfigCacheLoader($basedir);
$config = Factory\ConfigFactory::createCache($configLoader);
$logger = Factory\LoggerFactory::create('index', $config);
// We assume that the index.php is called by a frontend process
// The value is set to "true" by default in App
$a = new App($config, $logger, false);
$a->runFrontend(); $a->runFrontend();

View file

@ -1565,7 +1565,7 @@ function admin_page_site(App $a)
$storage_form = []; $storage_form = [];
if (!is_null($storage_current_backend) && $storage_current_backend != "") { if (!is_null($storage_current_backend) && $storage_current_backend != "") {
foreach($storage_current_backend::getOptions() as $name => $info) { foreach ($storage_current_backend::getOptions() as $name => $info) {
$type = $info[0]; $type = $info[0];
$info[0] = $storage_form_prefix . '_' . $name; $info[0] = $storage_form_prefix . '_' . $name;
$info['type'] = $type; $info['type'] = $type;

View file

@ -1,53 +0,0 @@
<?php
use Friendica\App;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Model\Group;
function contactgroup_content(App $a)
{
if (!local_user()) {
exit();
}
$change = null;
if (($a->argc > 2) && intval($a->argv[1]) && intval($a->argv[2])) {
$r = q("SELECT `id` FROM `contact` WHERE `id` = %d AND `uid` = %d and `self` = 0 and `blocked` = 0 AND `pending` = 0 LIMIT 1",
intval($a->argv[2]),
intval(local_user())
);
if (DBA::isResult($r)) {
$change = intval($a->argv[2]);
}
}
if (($a->argc > 1) && (intval($a->argv[1]))) {
$r = q("SELECT * FROM `group` WHERE `id` = %d AND `uid` = %d AND `deleted` = 0 LIMIT 1",
intval($a->argv[1]),
intval(local_user())
);
if (!DBA::isResult($r)) {
exit();
}
$group = $r[0];
$members = Contact::getByGroupId($group['id']);
$preselected = [];
if (count($members)) {
foreach ($members as $member) {
$preselected[] = $member['id'];
}
}
if (!empty($change)) {
if (in_array($change, $preselected)) {
Group::removeMember($group['id'], $change);
} else {
Group::addMember($group['id'], $change);
}
}
}
exit();
}

View file

@ -45,6 +45,8 @@ function directory_content(App $a)
} }
$o = ''; $o = '';
$entries = [];
Nav::setSelected('directory'); Nav::setSelected('directory');
if (!empty($a->data['search'])) { if (!empty($a->data['search'])) {
@ -98,7 +100,7 @@ function directory_content(App $a)
$limit = $pager->getStart()."," . $pager->getItemsPerPage(); $limit = $pager->getStart()."," . $pager->getItemsPerPage();
$r = DBA::p("SELECT `profile`.*, `profile`.`uid` AS `profile_uid`, `user`.`nickname`, `user`.`timezone` , `user`.`page-flags`, $r = DBA::p("SELECT `profile`.*, `profile`.`uid` AS `profile_uid`, `user`.`nickname`, `user`.`timezone` , `user`.`page-flags`,
`contact`.`addr`, `contact`.`url` AS profile_url FROM `profile` `contact`.`addr`, `contact`.`url` AS `profile_url` FROM `profile`
LEFT JOIN `user` ON `user`.`uid` = `profile`.`uid` LEFT JOIN `user` ON `user`.`uid` = `profile`.`uid`
LEFT JOIN `contact` ON `contact`.`uid` = `user`.`uid` LEFT JOIN `contact` ON `contact`.`uid` = `user`.`uid`
WHERE `is-default` $publish AND NOT `user`.`blocked` AND NOT `user`.`account_removed` AND `contact`.`self` WHERE `is-default` $publish AND NOT `user`.`blocked` AND NOT `user`.`account_removed` AND `contact`.`self`
@ -111,115 +113,118 @@ function directory_content(App $a)
$photo = 'photo'; $photo = 'photo';
} }
$entries = [];
while ($rr = DBA::fetch($r)) { while ($rr = DBA::fetch($r)) {
$itemurl = (($rr['addr'] != "") ? $rr['addr'] : $rr['profile_url']); $entries[] = format_directory_entry($rr, $photo);
$profile_link = $rr['profile_url'];
$pdesc = (($rr['pdesc']) ? $rr['pdesc'] . '<br />' : '');
$details = '';
if (strlen($rr['locality'])) {
$details .= $rr['locality'];
}
if (strlen($rr['region'])) {
if (strlen($rr['locality'])) {
$details .= ', ';
}
$details .= $rr['region'];
}
if (strlen($rr['country-name'])) {
if (strlen($details)) {
$details .= ', ';
}
$details .= $rr['country-name'];
}
// if(strlen($rr['dob'])) {
// if(($years = age($rr['dob'],$rr['timezone'],'')) != 0)
// $details .= '<br />' . L10n::t('Age: ') . $years;
// }
// if(strlen($rr['gender']))
// $details .= '<br />' . L10n::t('Gender: ') . $rr['gender'];
$profile = $rr;
if (!empty($profile['address'])
|| !empty($profile['locality'])
|| !empty($profile['region'])
|| !empty($profile['postal-code'])
|| !empty($profile['country-name'])
) {
$location = L10n::t('Location:');
} else {
$location = '';
}
$gender = (!empty($profile['gender']) ? L10n::t('Gender:') : false);
$marital = (!empty($profile['marital']) ? L10n::t('Status:') : false);
$homepage = (!empty($profile['homepage']) ? L10n::t('Homepage:') : false);
$about = (!empty($profile['about']) ? L10n::t('About:') : false);
$location_e = $location;
$photo_menu = [
'profile' => [L10n::t("View Profile"), Contact::magicLink($profile_link)]
];
$entry = [
'id' => $rr['id'],
'url' => Contact::magicLInk($profile_link),
'itemurl' => $itemurl,
'thumb' => ProxyUtils::proxifyUrl($rr[$photo], false, ProxyUtils::SIZE_THUMB),
'img_hover' => $rr['name'],
'name' => $rr['name'],
'details' => $details,
'account_type' => Contact::getAccountType($rr),
'profile' => $profile,
'location' => $location_e,
'tags' => $rr['pub_keywords'],
'gender' => $gender,
'pdesc' => $pdesc,
'marital' => $marital,
'homepage' => $homepage,
'about' => $about,
'photo_menu' => $photo_menu,
];
$arr = ['contact' => $rr, 'entry' => $entry];
Hook::callAll('directory_item', $arr);
unset($profile);
unset($location);
if (!$arr['entry']) {
continue;
}
$entries[] = $arr['entry'];
} }
DBA::close($r); DBA::close($r);
$tpl = Renderer::getMarkupTemplate('directory_header.tpl');
$o .= Renderer::replaceMacros($tpl, [
'$search' => $search,
'$globaldir' => L10n::t('Global Directory'),
'$gdirpath' => $gdirpath,
'$desc' => L10n::t('Find on this site'),
'$contacts' => $entries,
'$finding' => L10n::t('Results for:'),
'$findterm' => (strlen($search) ? $search : ""),
'$title' => L10n::t('Site Directory'),
'$submit' => L10n::t('Find'),
'$paginate' => $pager->renderFull($total),
]);
} else { } else {
info(L10n::t("No entries \x28some entries may be hidden\x29.") . EOL); info(L10n::t("No entries \x28some entries may be hidden\x29.") . EOL);
} }
$tpl = Renderer::getMarkupTemplate('directory_header.tpl');
$o .= Renderer::replaceMacros($tpl, [
'$search' => $search,
'$globaldir' => L10n::t('Global Directory'),
'$gdirpath' => $gdirpath,
'$desc' => L10n::t('Find on this site'),
'$contacts' => $entries,
'$finding' => L10n::t('Results for:'),
'$findterm' => (strlen($search) ? $search : ""),
'$title' => L10n::t('Site Directory'),
'$search_mod' => 'directory',
'$submit' => L10n::t('Find'),
'$paginate' => $pager->renderFull($total),
]);
return $o; return $o;
} }
/**
* Format contact/profile/user data from the database into an usable
* array for displaying directory entries.
*
* @param array $arr The directory entry from the database.
* @param string $photo_size Avatar size (thumb, photo or micro).
*
* @return array
*/
function format_directory_entry(array $arr, $photo_size = 'photo')
{
$itemurl = (($arr['addr'] != "") ? $arr['addr'] : $arr['profile_url']);
$profile_link = $arr['profile_url'];
$pdesc = (($arr['pdesc']) ? $arr['pdesc'] . '<br />' : '');
$details = '';
if (strlen($arr['locality'])) {
$details .= $arr['locality'];
}
if (strlen($arr['region'])) {
if (strlen($arr['locality'])) {
$details .= ', ';
}
$details .= $arr['region'];
}
if (strlen($arr['country-name'])) {
if (strlen($details)) {
$details .= ', ';
}
$details .= $arr['country-name'];
}
$profile = $arr;
if (!empty($profile['address'])
|| !empty($profile['locality'])
|| !empty($profile['region'])
|| !empty($profile['postal-code'])
|| !empty($profile['country-name'])
) {
$location = L10n::t('Location:');
} else {
$location = '';
}
$gender = (!empty($profile['gender']) ? L10n::t('Gender:') : false);
$marital = (!empty($profile['marital']) ? L10n::t('Status:') : false);
$homepage = (!empty($profile['homepage']) ? L10n::t('Homepage:') : false);
$about = (!empty($profile['about']) ? L10n::t('About:') : false);
$location_e = $location;
$photo_menu = [
'profile' => [L10n::t("View Profile"), Contact::magicLink($profile_link)]
];
$entry = [
'id' => $arr['id'],
'url' => Contact::magicLInk($profile_link),
'itemurl' => $itemurl,
'thumb' => ProxyUtils::proxifyUrl($arr[$photo_size], false, ProxyUtils::SIZE_THUMB),
'img_hover' => $arr['name'],
'name' => $arr['name'],
'details' => $details,
'account_type' => Contact::getAccountType($arr),
'profile' => $profile,
'location' => $location_e,
'tags' => $arr['pub_keywords'],
'gender' => $gender,
'pdesc' => $pdesc,
'marital' => $marital,
'homepage' => $homepage,
'about' => $about,
'photo_menu' => $photo_menu,
];
$hook = ['contact' => $arr, 'entry' => $entry];
Hook::callAll('directory_item', $hook);
unset($profile);
unset($location);
return $hook['entry'];
}

View file

@ -19,10 +19,10 @@ use Friendica\Model\Contact;
use Friendica\Model\Group; use Friendica\Model\Group;
use Friendica\Model\Item; use Friendica\Model\Item;
use Friendica\Model\Profile; use Friendica\Model\Profile;
use Friendica\Module\Objects;
use Friendica\Protocol\ActivityPub; use Friendica\Protocol\ActivityPub;
use Friendica\Protocol\DFRN; use Friendica\Protocol\DFRN;
use Friendica\Util\Strings; use Friendica\Util\Strings;
use Friendica\Module\Objects;
function display_init(App $a) function display_init(App $a)
{ {
@ -283,22 +283,25 @@ function display_content(App $a, $update = false, $update_uid = 0)
$is_remote_contact = false; $is_remote_contact = false;
$item_uid = local_user(); $item_uid = local_user();
$parent = Item::selectFirst(['uid'], ['uri' => $item_parent_uri, 'wall' => true]); if (isset($item_parent_uri)) {
if (DBA::isResult($parent)) { $parent = Item::selectFirst(['uid'], ['uri' => $item_parent_uri, 'wall' => true]);
$a->profile['uid'] = defaults($a->profile, 'uid', $parent['uid']); if (DBA::isResult($parent)) {
$a->profile['profile_uid'] = defaults($a->profile, 'profile_uid', $parent['uid']); $a->profile['uid'] = defaults($a->profile, 'uid', $parent['uid']);
$is_remote_contact = Contact::isFollower(remote_user(), $a->profile['profile_uid']); $a->profile['profile_uid'] = defaults($a->profile, 'profile_uid', $parent['uid']);
} $is_remote_contact = Contact::isFollower(remote_user(), $a->profile['profile_uid']);
if ($is_remote_contact) { if ($is_remote_contact) {
$cdata = Contact::getPublicAndUserContacID(remote_user(), $a->profile['profile_uid']); $cdata = Contact::getPublicAndUserContacID(remote_user(), $a->profile['profile_uid']);
if (!empty($cdata['user'])) { if (!empty($cdata['user'])) {
$groups = Group::getIdsByContactId($cdata['user']); $groups = Group::getIdsByContactId($cdata['user']);
$remote_cid = $cdata['user']; $remote_cid = $cdata['user'];
$item_uid = $parent['uid']; $item_uid = $parent['uid'];
}
}
} }
} }
$page_contact = DBA::selectFirst('contact', [], ['self' => true, 'uid' => $a->profile['uid']]); $page_contact = DBA::selectFirst('contact', [], ['self' => true, 'uid' => $a->profile['uid']]);
if (DBA::isResult($page_contact)) { if (DBA::isResult($page_contact)) {
$a->page_contact = $page_contact; $a->page_contact = $page_contact;

View file

@ -1,313 +0,0 @@
<?php
/**
* @file mod/group.php
* @brief The group module (create and rename contact groups, add and
* remove contacts to the contact groups
*/
use Friendica\App;
use Friendica\BaseModule;
use Friendica\Core\Config;
use Friendica\Core\L10n;
use Friendica\Core\PConfig;
use Friendica\Core\Renderer;
use Friendica\Database\DBA;
use Friendica\Model;
use Friendica\Module;
use Friendica\Util\Strings;
function group_init(App $a) {
if (local_user()) {
$a->page['aside'] = Model\Group::sidebarWidget('contact', 'group', 'extended', (($a->argc > 1) ? $a->argv[1] : 'everyone'));
}
}
function group_post(App $a) {
if (!local_user()) {
notice(L10n::t('Permission denied.') . EOL);
return;
}
if (($a->argc == 2) && ($a->argv[1] === 'new')) {
BaseModule::checkFormSecurityTokenRedirectOnError('/group/new', 'group_edit');
$name = Strings::escapeTags(trim($_POST['groupname']));
$r = Model\Group::create(local_user(), $name);
if ($r) {
info(L10n::t('Group created.') . EOL);
$r = Model\Group::getIdByName(local_user(), $name);
if ($r) {
$a->internalRedirect('group/' . $r);
}
} else {
notice(L10n::t('Could not create group.') . EOL);
}
$a->internalRedirect('group');
return; // NOTREACHED
}
if (($a->argc == 2) && intval($a->argv[1])) {
BaseModule::checkFormSecurityTokenRedirectOnError('/group', 'group_edit');
$r = q("SELECT * FROM `group` WHERE `id` = %d AND `uid` = %d LIMIT 1",
intval($a->argv[1]),
intval(local_user())
);
if (!DBA::isResult($r)) {
notice(L10n::t('Group not found.') . EOL);
$a->internalRedirect('contact');
return; // NOTREACHED
}
$group = $r[0];
$groupname = Strings::escapeTags(trim($_POST['groupname']));
if (strlen($groupname) && ($groupname != $group['name'])) {
$r = q("UPDATE `group` SET `name` = '%s' WHERE `uid` = %d AND `id` = %d",
DBA::escape($groupname),
intval(local_user()),
intval($group['id'])
);
if ($r) {
info(L10n::t('Group name changed.') . EOL);
}
}
$a->page['aside'] = Model\Group::sidebarWidget();
}
return;
}
function group_content(App $a) {
$change = false;
if (!local_user()) {
notice(L10n::t('Permission denied') . EOL);
return;
}
// With no group number provided we jump to the unassigned contacts as a starting point
if ($a->argc == 1) {
$a->internalRedirect('group/none');
}
// Switch to text mode interface if we have more than 'n' contacts or group members
$switchtotext = PConfig::get(local_user(), 'system', 'groupedit_image_limit');
if (is_null($switchtotext)) {
$switchtotext = Config::get('system', 'groupedit_image_limit', 400);
}
$tpl = Renderer::getMarkupTemplate('group_edit.tpl');
$context = [
'$submit' => L10n::t('Save Group'),
'$submit_filter' => L10n::t('Filter'),
];
if (($a->argc == 2) && ($a->argv[1] === 'new')) {
return Renderer::replaceMacros($tpl, $context + [
'$title' => L10n::t('Create a group of contacts/friends.'),
'$gname' => ['groupname', L10n::t('Group Name: '), '', ''],
'$gid' => 'new',
'$form_security_token' => BaseModule::getFormSecurityToken("group_edit"),
]);
}
$nogroup = false;
if (($a->argc == 2) && ($a->argv[1] === 'none')) {
$id = -1;
$nogroup = true;
$group = [
'id' => $id,
'name' => L10n::t('Contacts not in any group'),
];
$members = [];
$preselected = [];
$context = $context + [
'$title' => $group['name'],
'$gname' => ['groupname', L10n::t('Group Name: '), $group['name'], ''],
'$gid' => $id,
'$editable' => 0,
];
}
if (($a->argc == 3) && ($a->argv[1] === 'drop')) {
BaseModule::checkFormSecurityTokenRedirectOnError('/group', 'group_drop', 't');
if (intval($a->argv[2])) {
$r = q("SELECT `name` FROM `group` WHERE `id` = %d AND `uid` = %d LIMIT 1",
intval($a->argv[2]),
intval(local_user())
);
$result = null;
if (DBA::isResult($r)) {
$result = Model\Group::removeByName(local_user(), $r[0]['name']);
}
if ($result) {
info(L10n::t('Group removed.') . EOL);
} else {
notice(L10n::t('Unable to remove group.') . EOL);
}
}
$a->internalRedirect('group');
// NOTREACHED
}
if (($a->argc > 2) && intval($a->argv[1]) && intval($a->argv[2])) {
BaseModule::checkFormSecurityTokenForbiddenOnError('group_member_change', 't');
$r = q("SELECT `id` FROM `contact` WHERE `id` = %d AND `uid` = %d and `self` = 0 and `blocked` = 0 AND `pending` = 0 LIMIT 1",
intval($a->argv[2]),
intval(local_user())
);
if (DBA::isResult($r)) {
$change = intval($a->argv[2]);
}
}
if (($a->argc > 1) && intval($a->argv[1])) {
$r = q("SELECT * FROM `group` WHERE `id` = %d AND `uid` = %d AND `deleted` = 0 LIMIT 1",
intval($a->argv[1]),
intval(local_user())
);
if (!DBA::isResult($r)) {
notice(L10n::t('Group not found.') . EOL);
$a->internalRedirect('contact');
}
$group = $r[0];
$members = Model\Contact::getByGroupId($group['id']);
$preselected = [];
if (count($members)) {
foreach ($members as $member) {
$preselected[] = $member['id'];
}
}
if ($change) {
if (in_array($change, $preselected)) {
Model\Group::removeMember($group['id'], $change);
} else {
Model\Group::addMember($group['id'], $change);
}
$members = Model\Contact::getByGroupId($group['id']);
$preselected = [];
if (count($members)) {
foreach ($members as $member) {
$preselected[] = $member['id'];
}
}
}
$drop_tpl = Renderer::getMarkupTemplate('group_drop.tpl');
$drop_txt = Renderer::replaceMacros($drop_tpl, [
'$id' => $group['id'],
'$delete' => L10n::t('Delete Group'),
'$form_security_token' => BaseModule::getFormSecurityToken("group_drop"),
]);
$context = $context + [
'$title' => $group['name'],
'$gname' => ['groupname', L10n::t('Group Name: '), $group['name'], ''],
'$gid' => $group['id'],
'$drop' => $drop_txt,
'$form_security_token' => BaseModule::getFormSecurityToken('group_edit'),
'$edit_name' => L10n::t('Edit Group Name'),
'$editable' => 1,
];
}
if (!isset($group)) {
return;
}
$groupeditor = [
'label_members' => L10n::t('Members'),
'members' => [],
'label_contacts' => L10n::t('All Contacts'),
'group_is_empty' => L10n::t('Group is empty'),
'contacts' => [],
];
$sec_token = addslashes(BaseModule::getFormSecurityToken('group_member_change'));
// Format the data of the group members
foreach ($members as $member) {
if ($member['url']) {
$entry = Module\Contact::getContactTemplateVars($member);
$entry['label'] = 'members';
$entry['photo_menu'] = '';
$entry['change_member'] = [
'title' => L10n::t("Remove contact from group"),
'gid' => $group['id'],
'cid' => $member['id'],
'sec_token' => $sec_token
];
$groupeditor['members'][] = $entry;
} else {
Model\Group::removeMember($group['id'], $member['id']);
}
}
if ($nogroup) {
$r = Model\Contact::getUngroupedList(local_user());
} else {
$r = q("SELECT * FROM `contact` WHERE `uid` = %d AND NOT `blocked` AND NOT `pending` AND NOT `self` ORDER BY `name` ASC",
intval(local_user())
);
$context['$desc'] = L10n::t('Click on a contact to add or remove.');
}
if (DBA::isResult($r)) {
// Format the data of the contacts who aren't in the contact group
foreach ($r as $member) {
if (!in_array($member['id'], $preselected)) {
$entry = Module\Contact::getContactTemplateVars($member);
$entry['label'] = 'contacts';
if (!$nogroup)
$entry['photo_menu'] = [];
if (!$nogroup) {
$entry['change_member'] = [
'title' => L10n::t("Add contact to group"),
'gid' => $group['id'],
'cid' => $member['id'],
'sec_token' => $sec_token
];
}
$groupeditor['contacts'][] = $entry;
}
}
}
$context['$groupeditor'] = $groupeditor;
// If there are to many contacts we could provide an alternative view mode
$total = count($groupeditor['members']) + count($groupeditor['contacts']);
$context['$shortmode'] = (($switchtotext && ($total > $switchtotext)) ? true : false);
if ($change) {
$tpl = Renderer::getMarkupTemplate('groupeditor.tpl');
echo Renderer::replaceMacros($tpl, $context);
exit();
}
return Renderer::replaceMacros($tpl, $context);
}

View file

@ -33,6 +33,7 @@ use Friendica\Model\FileTag;
use Friendica\Model\Item; use Friendica\Model\Item;
use Friendica\Model\Photo; use Friendica\Model\Photo;
use Friendica\Model\Attach; use Friendica\Model\Attach;
use Friendica\Model\Term;
use Friendica\Protocol\Diaspora; use Friendica\Protocol\Diaspora;
use Friendica\Protocol\Email; use Friendica\Protocol\Email;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
@ -83,13 +84,13 @@ function item_post(App $a) {
} }
// Is this a reply to something? // Is this a reply to something?
$thr_parent = intval(defaults($_REQUEST, 'parent', 0)); $toplevel_item_id = intval(defaults($_REQUEST, 'parent', 0));
$thr_parent_uri = trim(defaults($_REQUEST, 'parent_uri', '')); $thr_parent_uri = trim(defaults($_REQUEST, 'parent_uri', ''));
$thr_parent_contact = null; $thread_parent_id = 0;
$thread_parent_contact = null;
$parent = 0; $toplevel_item = null;
$parent_item = null;
$parent_user = null; $parent_user = null;
$parent_contact = null; $parent_contact = null;
@ -98,25 +99,26 @@ function item_post(App $a) {
$profile_uid = defaults($_REQUEST, 'profile_uid', local_user()); $profile_uid = defaults($_REQUEST, 'profile_uid', local_user());
$posttype = defaults($_REQUEST, 'post_type', Item::PT_ARTICLE); $posttype = defaults($_REQUEST, 'post_type', Item::PT_ARTICLE);
if ($thr_parent || $thr_parent_uri) { if ($toplevel_item_id || $thr_parent_uri) {
if ($thr_parent) { if ($toplevel_item_id) {
$parent_item = Item::selectFirst([], ['id' => $thr_parent]); $toplevel_item = Item::selectFirst([], ['id' => $toplevel_item_id]);
} elseif ($thr_parent_uri) { } elseif ($thr_parent_uri) {
$parent_item = Item::selectFirst([], ['uri' => $thr_parent_uri, 'uid' => $profile_uid]); $toplevel_item = Item::selectFirst([], ['uri' => $thr_parent_uri, 'uid' => $profile_uid]);
} }
// if this isn't the real parent of the conversation, find it // if this isn't the top-level parent of the conversation, find it
if (DBA::isResult($parent_item)) { if (DBA::isResult($toplevel_item)) {
// The URI and the contact is taken from the direct parent which needn't to be the top parent // The URI and the contact is taken from the direct parent which needn't to be the top parent
$thr_parent_uri = $parent_item['uri']; $thread_parent_id = $toplevel_item['id'];
$thr_parent_contact = Contact::getDetailsByURL($parent_item["author-link"]); $thr_parent_uri = $toplevel_item['uri'];
$thread_parent_contact = Contact::getDetailsByURL($toplevel_item["author-link"]);
if ($parent_item['id'] != $parent_item['parent']) { if ($toplevel_item['id'] != $toplevel_item['parent']) {
$parent_item = Item::selectFirst(Item::ITEM_FIELDLIST, ['id' => $parent_item['parent']]); $toplevel_item = Item::selectFirst(Item::ITEM_FIELDLIST, ['id' => $toplevel_item['parent']]);
} }
} }
if (!DBA::isResult($parent_item)) { if (!DBA::isResult($toplevel_item)) {
notice(L10n::t('Unable to locate original post.') . EOL); notice(L10n::t('Unable to locate original post.') . EOL);
if (!empty($_REQUEST['return'])) { if (!empty($_REQUEST['return'])) {
$a->internalRedirect($return_path); $a->internalRedirect($return_path);
@ -124,14 +126,14 @@ function item_post(App $a) {
exit(); exit();
} }
$parent = $parent_item['id']; $toplevel_item_id = $toplevel_item['id'];
$parent_user = $parent_item['uid']; $parent_user = $toplevel_item['uid'];
$objecttype = ACTIVITY_OBJ_COMMENT; $objecttype = ACTIVITY_OBJ_COMMENT;
} }
if ($parent) { if ($toplevel_item_id) {
Logger::log('mod_item: item_post parent=' . $parent); Logger::info('mod_item: item_post parent=' . $toplevel_item_id);
} }
$post_id = intval(defaults($_REQUEST, 'post_id', 0)); $post_id = intval(defaults($_REQUEST, 'post_id', 0));
@ -160,7 +162,7 @@ function item_post(App $a) {
} }
// Allow commenting if it is an answer to a public post // Allow commenting if it is an answer to a public post
$allow_comment = local_user() && ($profile_uid == 0) && $parent && in_array($parent_item['network'], [Protocol::ACTIVITYPUB, Protocol::OSTATUS, Protocol::DIASPORA, Protocol::DFRN]); $allow_comment = local_user() && ($profile_uid == 0) && $toplevel_item_id && in_array($toplevel_item['network'], [Protocol::ACTIVITYPUB, Protocol::OSTATUS, Protocol::DIASPORA, Protocol::DFRN]);
// Now check that valid personal details have been provided // Now check that valid personal details have been provided
if (!Security::canWriteToUserWall($profile_uid) && !$allow_comment) { if (!Security::canWriteToUserWall($profile_uid) && !$allow_comment) {
@ -183,7 +185,7 @@ function item_post(App $a) {
$user = DBA::selectFirst('user', [], ['uid' => $profile_uid]); $user = DBA::selectFirst('user', [], ['uid' => $profile_uid]);
if (!DBA::isResult($user) && !$parent) { if (!DBA::isResult($user) && !$toplevel_item_id) {
return 0; return 0;
} }
@ -287,21 +289,21 @@ function item_post(App $a) {
// If this is a comment, set the permissions from the parent. // If this is a comment, set the permissions from the parent.
if ($parent_item) { if ($toplevel_item) {
// for non native networks use the network of the original post as network of the item // for non native networks use the network of the original post as network of the item
if (($parent_item['network'] != Protocol::DIASPORA) if (($toplevel_item['network'] != Protocol::DIASPORA)
&& ($parent_item['network'] != Protocol::OSTATUS) && ($toplevel_item['network'] != Protocol::OSTATUS)
&& ($network == "")) { && ($network == "")) {
$network = $parent_item['network']; $network = $toplevel_item['network'];
} }
$str_contact_allow = $parent_item['allow_cid']; $str_contact_allow = $toplevel_item['allow_cid'];
$str_group_allow = $parent_item['allow_gid']; $str_group_allow = $toplevel_item['allow_gid'];
$str_contact_deny = $parent_item['deny_cid']; $str_contact_deny = $toplevel_item['deny_cid'];
$str_group_deny = $parent_item['deny_gid']; $str_group_deny = $toplevel_item['deny_gid'];
$private = $parent_item['private']; $private = $toplevel_item['private'];
$wall = $parent_item['wall']; $wall = $toplevel_item['wall'];
} }
$pubmail_enabled = defaults($_REQUEST, 'pubmail_enable', false) && !$private; $pubmail_enabled = defaults($_REQUEST, 'pubmail_enable', false) && !$private;
@ -382,12 +384,8 @@ function item_post(App $a) {
$tags = BBCode::getTags($body); $tags = BBCode::getTags($body);
// Add a tag if the parent contact is from ActivityPub or OStatus (This will notify them) if ($thread_parent_id && !\Friendica\Content\Feature::isEnabled($uid, 'explicit_mentions')) {
if ($parent && in_array($thr_parent_contact['network'], [Protocol::OSTATUS, Protocol::ACTIVITYPUB])) { $tags = item_add_implicit_mentions($tags, $thread_parent_contact, $thread_parent_id);
$contact = '@[url=' . $thr_parent_contact['url'] . ']' . $thr_parent_contact['nick'] . '[/url]';
if (!stripos(implode($tags), '[url=' . $thr_parent_contact['url'] . ']')) {
$tags[] = $contact;
}
} }
$tagged = []; $tagged = [];
@ -400,7 +398,7 @@ function item_post(App $a) {
foreach ($tags as $tag) { foreach ($tags as $tag) {
$tag_type = substr($tag, 0, 1); $tag_type = substr($tag, 0, 1);
if ($tag_type == '#') { if ($tag_type == Term::TAG_CHARACTER[Term::HASHTAG]) {
continue; continue;
} }
@ -425,9 +423,9 @@ function item_post(App $a) {
$tagged[] = $tag; $tagged[] = $tag;
} }
// When the forum is private or the forum is addressed with a "!" make the post private // When the forum is private or the forum is addressed with a "!" make the post private
if (is_array($success['contact']) && (!empty($success['contact']['prv']) || ($tag_type == '!'))) { if (is_array($success['contact']) && (!empty($success['contact']['prv']) || ($tag_type == Term::TAG_CHARACTER[Term::EXCLUSIVE_MENTION]))) {
$private_forum = $success['contact']['prv']; $private_forum = $success['contact']['prv'];
$only_to_forum = ($tag_type == '!'); $only_to_forum = ($tag_type == Term::TAG_CHARACTER[Term::EXCLUSIVE_MENTION]);
$private_id = $success['contact']['id']; $private_id = $success['contact']['id'];
$forum_contact = $success['contact']; $forum_contact = $success['contact'];
} elseif (is_array($success['contact']) && !empty($success['contact']['forum']) && } elseif (is_array($success['contact']) && !empty($success['contact']['forum']) &&
@ -442,7 +440,7 @@ function item_post(App $a) {
$original_contact_id = $contact_id; $original_contact_id = $contact_id;
if (!$parent && count($forum_contact) && ($private_forum || $only_to_forum)) { if (!$toplevel_item_id && count($forum_contact) && ($private_forum || $only_to_forum)) {
// we tagged a forum in a top level post. Now we change the post // we tagged a forum in a top level post. Now we change the post
$private = $private_forum; $private = $private_forum;
@ -595,7 +593,7 @@ function item_post(App $a) {
$network = Protocol::DFRN; $network = Protocol::DFRN;
} }
$gravity = ($parent ? GRAVITY_COMMENT : GRAVITY_PARENT); $gravity = ($toplevel_item_id ? GRAVITY_COMMENT : GRAVITY_PARENT);
// even if the post arrived via API we are considering that it // even if the post arrived via API we are considering that it
// originated on this site by default for determining relayability. // originated on this site by default for determining relayability.
@ -607,12 +605,12 @@ function item_post(App $a) {
$origin = $_REQUEST['origin']; $origin = $_REQUEST['origin'];
} }
$notify_type = ($parent ? 'comment-new' : 'wall-new'); $notify_type = ($toplevel_item_id ? 'comment-new' : 'wall-new');
$uri = ($message_id ? $message_id : Item::newURI($api_source ? $profile_uid : $uid, $guid)); $uri = ($message_id ? $message_id : Item::newURI($api_source ? $profile_uid : $uid, $guid));
// Fallback so that we alway have a parent uri // Fallback so that we alway have a parent uri
if (!$thr_parent_uri || !$parent) { if (!$thr_parent_uri || !$toplevel_item_id) {
$thr_parent_uri = $uri; $thr_parent_uri = $uri;
} }
@ -670,7 +668,7 @@ function item_post(App $a) {
* 'self' if true indicates the owner is posting on their own wall * 'self' if true indicates the owner is posting on their own wall
* If parent is 0 it is a top-level post. * If parent is 0 it is a top-level post.
*/ */
$datarray['parent'] = $parent; $datarray['parent'] = $toplevel_item_id;
$datarray['self'] = $self; $datarray['self'] = $self;
// This triggers posts via API and the mirror functions // This triggers posts via API and the mirror functions
@ -788,7 +786,7 @@ function item_post(App $a) {
FileTag::updatePconfig($uid, $categories_old, $categories_new, 'category'); FileTag::updatePconfig($uid, $categories_old, $categories_new, 'category');
// These notifications are sent if someone else is commenting other your wall // These notifications are sent if someone else is commenting other your wall
if ($parent) { if ($toplevel_item_id) {
if ($contact_record != $author) { if ($contact_record != $author) {
notification([ notification([
'type' => NOTIFY_COMMENT, 'type' => NOTIFY_COMMENT,
@ -804,8 +802,8 @@ function item_post(App $a) {
'source_photo' => $datarray['author-avatar'], 'source_photo' => $datarray['author-avatar'],
'verb' => ACTIVITY_POST, 'verb' => ACTIVITY_POST,
'otype' => 'item', 'otype' => 'item',
'parent' => $parent, 'parent' => $toplevel_item_id,
'parent_uri' => $parent_item['uri'] 'parent_uri' => $toplevel_item['uri']
]); ]);
} }
} else { } else {
@ -962,7 +960,7 @@ function handle_tag(&$body, &$inform, &$str_tags, $profile_uid, $tag, $network =
$r = null; $r = null;
//is it a person tag? //is it a person tag?
if ((strpos($tag, '@') === 0) || (strpos($tag, '!') === 0)) { if (Term::isType($tag, Term::MENTION, Term::IMPLICIT_MENTION, Term::EXCLUSIVE_MENTION)) {
$tag_type = substr($tag, 0, 1); $tag_type = substr($tag, 0, 1);
//is it already replaced? //is it already replaced?
if (strpos($tag, '[url=')) { if (strpos($tag, '[url=')) {
@ -1099,3 +1097,34 @@ function handle_tag(&$body, &$inform, &$str_tags, $profile_uid, $tag, $network =
return ['replaced' => $replaced, 'contact' => $contact]; return ['replaced' => $replaced, 'contact' => $contact];
} }
function item_add_implicit_mentions(array $tags, array $thread_parent_contact, $thread_parent_id)
{
if (Config::get('system', 'disable_implicit_mentions')) {
// Add a tag if the parent contact is from ActivityPub or OStatus (This will notify them)
if (in_array($thread_parent_contact['network'], [Protocol::OSTATUS, Protocol::ACTIVITYPUB])) {
$contact = Term::TAG_CHARACTER[Term::MENTION] . '[url=' . $thread_parent_contact['url'] . ']' . $thread_parent_contact['nick'] . '[/url]';
if (!stripos(implode($tags), '[url=' . $thread_parent_contact['url'] . ']')) {
$tags[] = $contact;
}
}
} else {
$implicit_mentions = [
$thread_parent_contact['url'] => $thread_parent_contact['nick']
];
$parent_terms = Term::tagArrayFromItemId($thread_parent_id, [Term::MENTION, Term::IMPLICIT_MENTION]);
foreach ($parent_terms as $parent_term) {
$implicit_mentions[$parent_term['url']] = $parent_term['term'];
}
foreach ($implicit_mentions as $url => $label) {
if ($url != \Friendica\Model\Profile::getMyURL() && !stripos(implode($tags), '[url=' . $url . ']')) {
$tags[] = Term::TAG_CHARACTER[Term::IMPLICIT_MENTION] . '[url=' . $url . ']' . $label . '[/url]';
}
}
}
return $tags;
}

View file

@ -12,7 +12,12 @@ use Friendica\Core\Logger;
use Friendica\Core\System; use Friendica\Core\System;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\Util\Network; use Friendica\Util\Network;
function nodeinfo_wellknown(App $a) { function nodeinfo_wellknown(App $a) {
if (!Config::get('system', 'nodeinfo')) {
System::httpExit(404);
}
$nodeinfo = ['links' => [['rel' => 'http://nodeinfo.diaspora.software/ns/schema/1.0', $nodeinfo = ['links' => [['rel' => 'http://nodeinfo.diaspora.software/ns/schema/1.0',
'href' => System::baseUrl().'/nodeinfo/1.0']]]; 'href' => System::baseUrl().'/nodeinfo/1.0']]];

View file

@ -338,7 +338,7 @@ function profiles_post(App $a) {
$hide_friends = (($_POST['hide-friends'] == 1) ? 1: 0); $hide_friends = (($_POST['hide-friends'] == 1) ? 1: 0);
PConfig::set(local_user(), 'system', 'detailled_profile', (($_POST['detailled_profile'] == 1) ? 1: 0)); PConfig::set(local_user(), 'system', 'detailled_profile', (($_POST['detailed_profile'] == 1) ? 1: 0));
$changes = []; $changes = [];
if ($is_default) { if ($is_default) {
@ -535,18 +535,18 @@ function profiles_content(App $a) {
$personal_account = !(in_array($a->user["page-flags"], $personal_account = !(in_array($a->user["page-flags"],
[User::PAGE_FLAGS_COMMUNITY, User::PAGE_FLAGS_PRVGROUP])); [User::PAGE_FLAGS_COMMUNITY, User::PAGE_FLAGS_PRVGROUP]));
$detailled_profile = (PConfig::get(local_user(), 'system', 'detailled_profile') AND $personal_account); $detailed_profile = (PConfig::get(local_user(), 'system', 'detailled_profile') AND $personal_account);
$is_default = (($r[0]['is-default']) ? 1 : 0); $is_default = (($r[0]['is-default']) ? 1 : 0);
$tpl = Renderer::getMarkupTemplate("profile_edit.tpl"); $tpl = Renderer::getMarkupTemplate("profile_edit.tpl");
$o .= Renderer::replaceMacros($tpl, [ $o .= Renderer::replaceMacros($tpl, [
'$personal_account' => $personal_account, '$personal_account' => $personal_account,
'$detailled_profile' => $detailled_profile, '$detailled_profile' => $detailed_profile,
'$details' => [ '$details' => [
'detailled_profile', //Name 'detailed_profile', //Name
L10n::t('Show more profile fields:'), //Label L10n::t('Show more profile fields:'), //Label
$detailled_profile, //Value $detailed_profile, //Value
'', //Help string '', //Help string
[L10n::t('No'), L10n::t('Yes')] //Off - On strings [L10n::t('No'), L10n::t('Yes')] //Off - On strings
], ],

View file

@ -8,11 +8,12 @@ use Detection\MobileDetect;
use DOMDocument; use DOMDocument;
use DOMXPath; use DOMXPath;
use Exception; use Exception;
use Friendica\Core\Config\ConfigCache; use Friendica\Core\Config\Cache\ConfigCacheLoader;
use Friendica\Core\Config\ConfigCacheLoader; use Friendica\Core\Config\Cache\IConfigCache;
use Friendica\Core\Config\Configuration;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\Factory\ConfigFactory;
use Friendica\Network\HTTPException\InternalServerErrorException; use Friendica\Network\HTTPException\InternalServerErrorException;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
/** /**
@ -53,8 +54,6 @@ class App
public $identities; public $identities;
public $is_mobile = false; public $is_mobile = false;
public $is_tablet = false; public $is_tablet = false;
public $performance = [];
public $callstack = [];
public $theme_info = []; public $theme_info = [];
public $category; public $category;
// Allow themes to control internal parameters // Allow themes to control internal parameters
@ -110,23 +109,28 @@ class App
public $mobileDetect; public $mobileDetect;
/** /**
* @var LoggerInterface The current logger of this App * @var Configuration The config
*/
private $logger;
/**
* @var ConfigCache The cached config
*/ */
private $config; private $config;
/**
* @var LoggerInterface The logger
*/
private $logger;
/**
* @var Profiler The profiler of this app
*/
private $profiler;
/** /**
* Returns the current config cache of this node * Returns the current config cache of this node
* *
* @return ConfigCache * @return IConfigCache
*/ */
public function getConfig() public function getConfigCache()
{ {
return $this->config; return $this->config->getCache();
} }
/** /**
@ -139,6 +143,26 @@ class App
return $this->basePath; return $this->basePath;
} }
/**
* The Logger of this app
*
* @return LoggerInterface
*/
public function getLogger()
{
return $this->logger;
}
/**
* The profiler of this app
*
* @return Profiler
*/
public function getProfiler()
{
return $this->profiler;
}
/** /**
* Register a stylesheet file path to be included in the <head> tag of every page. * Register a stylesheet file path to be included in the <head> tag of every page.
* Inclusion is done in App->initHead(). * Inclusion is done in App->initHead().
@ -173,7 +197,6 @@ class App
$this->footerScripts[] = trim($url, '/'); $this->footerScripts[] = trim($url, '/');
} }
public $process_id;
public $queue; public $queue;
private $scheme; private $scheme;
private $hostname; private $hostname;
@ -181,48 +204,33 @@ class App
/** /**
* @brief App constructor. * @brief App constructor.
* *
* @param ConfigCache $config The Cached Config * @param string $basePath The basedir of the app
* @param LoggerInterface $logger Logger of this application * @param Configuration $config The Configuration
* @param LoggerInterface $logger The current app logger
* @param Profiler $profiler The profiler of this application
* @param bool $isBackend Whether it is used for backend or frontend (Default true=backend) * @param bool $isBackend Whether it is used for backend or frontend (Default true=backend)
* *
* @throws Exception if the Basepath is not usable * @throws Exception if the Basepath is not usable
*/ */
public function __construct(ConfigCache $config, LoggerInterface $logger, $isBackend = true) public function __construct($basePath, Configuration $config, LoggerInterface $logger, Profiler $profiler, $isBackend = true)
{ {
$this->config = $config; BaseObject::setApp($this);
$this->logger = $logger; $this->logger = $logger;
$this->basePath = $this->config->get('system', 'basepath'); $this->config = $config;
$this->profiler = $profiler;
$cfgBasePath = $this->config->get('system', 'basepath');
$this->basePath = !empty($cfgBasePath) ? $cfgBasePath : $basePath;
if (!Core\System::isDirectoryUsable($this->basePath, false)) { if (!Core\System::isDirectoryUsable($this->basePath, false)) {
throw new Exception('Basepath ' . $this->basePath . ' isn\'t usable.'); throw new Exception('Basepath \'' . $this->basePath . '\' isn\'t usable.');
} }
$this->basePath = rtrim($this->basePath, DIRECTORY_SEPARATOR); $this->basePath = rtrim($this->basePath, DIRECTORY_SEPARATOR);
BaseObject::setApp($this);
$this->checkBackend($isBackend); $this->checkBackend($isBackend);
$this->checkFriendicaApp(); $this->checkFriendicaApp();
$this->performance['start'] = microtime(true); $this->profiler->reset();
$this->performance['database'] = 0;
$this->performance['database_write'] = 0;
$this->performance['cache'] = 0;
$this->performance['cache_write'] = 0;
$this->performance['network'] = 0;
$this->performance['file'] = 0;
$this->performance['rendering'] = 0;
$this->performance['parser'] = 0;
$this->performance['marktime'] = 0;
$this->performance['markstart'] = microtime(true);
$this->callstack['database'] = [];
$this->callstack['database_write'] = [];
$this->callstack['cache'] = [];
$this->callstack['cache_write'] = [];
$this->callstack['network'] = [];
$this->callstack['file'] = [];
$this->callstack['rendering'] = [];
$this->callstack['parser'] = [];
$this->mode = new App\Mode($this->basePath); $this->mode = new App\Mode($this->basePath);
@ -341,61 +349,30 @@ class App
return $this->mode; return $this->mode;
} }
/**
* Returns the Logger of the Application
*
* @return LoggerInterface The Logger
* @throws InternalServerErrorException when the logger isn't created
*/
public function getLogger()
{
if (empty($this->logger)) {
throw new InternalServerErrorException('Logger of the Application is not defined');
}
return $this->logger;
}
/** /**
* Reloads the whole app instance * Reloads the whole app instance
*/ */
public function reload() public function reload()
{ {
Core\Config::init($this->config);
Core\PConfig::init($this->config);
$this->loadDatabase();
$this->getMode()->determine($this->basePath);
$this->determineURLPath(); $this->determineURLPath();
if ($this->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
$adapterType = $this->config->get('system', 'config_adapter');
$adapter = ConfigFactory::createConfig($adapterType, $this->config);
Core\Config::setAdapter($adapter);
$adapterP = ConfigFactory::createPConfig($adapterType, $this->config);
Core\PConfig::setAdapter($adapterP);
Core\Config::load();
}
// again because DB-config could change the config
$this->getMode()->determine($this->basePath); $this->getMode()->determine($this->basePath);
if ($this->getMode()->has(App\Mode::DBAVAILABLE)) { if ($this->getMode()->has(App\Mode::DBAVAILABLE)) {
Core\Hook::loadHooks();
$loader = new ConfigCacheLoader($this->basePath); $loader = new ConfigCacheLoader($this->basePath);
$this->config->getCache()->load($loader->loadCoreConfig('addon'), true);
$this->profiler->update(
$this->config->get('system', 'profiler', false),
$this->config->get('rendertime', 'callstack', false));
Core\Hook::loadHooks();
Core\Hook::callAll('load_config', $loader); Core\Hook::callAll('load_config', $loader);
$this->config->loadConfigArray($loader->loadCoreConfig('addon'), true);
} }
$this->loadDefaultTimezone(); $this->loadDefaultTimezone();
Core\L10n::init(); Core\L10n::init();
$this->process_id = Core\System::processID('log');
Core\Logger::setLogger($this->logger);
} }
/** /**
@ -424,16 +401,25 @@ class App
*/ */
private function determineURLPath() private function determineURLPath()
{ {
/*
* 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 /* Relative script path to the web server root
* Not all of those $_SERVER properties can be present, so we do by inverse priority order * Not all of those $_SERVER properties can be present, so we do by inverse priority order
*/ */
/*
$relative_script_path = ''; $relative_script_path = '';
$relative_script_path = defaults($_SERVER, 'REDIRECT_URL' , $relative_script_path); $relative_script_path = defaults($_SERVER, 'REDIRECT_URL' , $relative_script_path);
$relative_script_path = defaults($_SERVER, 'REDIRECT_URI' , $relative_script_path); $relative_script_path = defaults($_SERVER, 'REDIRECT_URI' , $relative_script_path);
$relative_script_path = defaults($_SERVER, 'REDIRECT_SCRIPT_URL', $relative_script_path); $relative_script_path = defaults($_SERVER, 'REDIRECT_SCRIPT_URL', $relative_script_path);
$relative_script_path = defaults($_SERVER, 'SCRIPT_URL' , $relative_script_path); $relative_script_path = defaults($_SERVER, 'SCRIPT_URL' , $relative_script_path);
$relative_script_path = defaults($_SERVER, 'REQUEST_URI' , $relative_script_path); $relative_script_path = defaults($_SERVER, 'REQUEST_URI' , $relative_script_path);
*/
$this->urlPath = $this->config->get('system', 'urlpath'); $this->urlPath = $this->config->get('system', 'urlpath');
/* $relative_script_path gives /relative/path/to/friendica/module/parameter /* $relative_script_path gives /relative/path/to/friendica/module/parameter
@ -441,6 +427,7 @@ class App
* *
* To get /relative/path/to/friendica we perform dirname() for as many levels as there are slashes in the QUERY_STRING * 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)) { if (!empty($relative_script_path)) {
// Module // Module
if (!empty($_SERVER['QUERY_STRING'])) { if (!empty($_SERVER['QUERY_STRING'])) {
@ -454,49 +441,7 @@ class App
$this->urlPath = $path; $this->urlPath = $path;
} }
} }
} */
public function loadDatabase()
{
if (DBA::connected()) {
return;
}
$db_host = $this->config->get('database', 'hostname');
$db_user = $this->config->get('database', 'username');
$db_pass = $this->config->get('database', 'password');
$db_data = $this->config->get('database', 'database');
$charset = $this->config->get('database', 'charset');
// Use environment variables for mysql if they are set beforehand
if (!empty(getenv('MYSQL_HOST'))
&& !empty(getenv('MYSQL_USERNAME') || !empty(getenv('MYSQL_USER')))
&& getenv('MYSQL_PASSWORD') !== false
&& !empty(getenv('MYSQL_DATABASE')))
{
$db_host = getenv('MYSQL_HOST');
if (!empty(getenv('MYSQL_PORT'))) {
$db_host .= ':' . getenv('MYSQL_PORT');
}
if (!empty(getenv('MYSQL_USERNAME'))) {
$db_user = getenv('MYSQL_USERNAME');
} else {
$db_user = getenv('MYSQL_USER');
}
$db_pass = (string) getenv('MYSQL_PASSWORD');
$db_data = getenv('MYSQL_DATABASE');
}
$stamp1 = microtime(true);
if (DBA::connect($this->config, $db_host, $db_user, $db_pass, $db_data, $charset)) {
// Loads DB_UPDATE_VERSION constant
Database\DBStructure::definition($this->basePath, false);
}
unset($db_host, $db_user, $db_pass, $db_data, $charset);
$this->saveTimestamp($stamp1, 'network');
} }
public function getScheme() public function getScheme()
@ -663,8 +608,6 @@ class App
'$local_user' => local_user(), '$local_user' => local_user(),
'$generator' => 'Friendica' . ' ' . FRIENDICA_VERSION, '$generator' => 'Friendica' . ' ' . FRIENDICA_VERSION,
'$delitem' => Core\L10n::t('Delete this item?'), '$delitem' => Core\L10n::t('Delete this item?'),
'$showmore' => Core\L10n::t('show more'),
'$showfewer' => Core\L10n::t('show fewer'),
'$update_interval' => $interval, '$update_interval' => $interval,
'$shortcut_icon' => $shortcut_icon, '$shortcut_icon' => $shortcut_icon,
'$touch_icon' => $touch_icon, '$touch_icon' => $touch_icon,
@ -742,41 +685,6 @@ class App
} }
} }
/**
* Saves a timestamp for a value - f.e. a call
* Necessary for profiling Friendica
*
* @param int $timestamp the Timestamp
* @param string $value A value to profile
*/
public function saveTimestamp($timestamp, $value)
{
$profiler = $this->config->get('system', 'profiler');
if (!isset($profiler) || !$profiler) {
return;
}
$duration = (float) (microtime(true) - $timestamp);
if (!isset($this->performance[$value])) {
// Prevent ugly E_NOTICE
$this->performance[$value] = 0;
}
$this->performance[$value] += (float) $duration;
$this->performance['marktime'] += (float) $duration;
$callstack = Core\System::callstack();
if (!isset($this->callstack[$value][$callstack])) {
// Prevent ugly E_NOTICE
$this->callstack[$value][$callstack] = 0;
}
$this->callstack[$value][$callstack] += (float) $duration;
}
/** /**
* Returns the current UserAgent as a String * Returns the current UserAgent as a String
* *
@ -1227,7 +1135,7 @@ class App
if (!$this->isBackend()) { if (!$this->isBackend()) {
$stamp1 = microtime(true); $stamp1 = microtime(true);
session_start(); session_start();
$this->saveTimestamp($stamp1, 'parser'); $this->profiler->saveTimestamp($stamp1, 'parser', Core\System::callstack());
Core\L10n::setSessionVariable(); Core\L10n::setSessionVariable();
Core\L10n::setLangFromSession(); Core\L10n::setLangFromSession();
} else { } else {

View file

@ -150,7 +150,7 @@ class ContactSelector
{ {
$o = ''; $o = '';
$select = [ $select = [
'EMPTY' => '', '' => L10n::t('No answer'),
'Male' => L10n::t('Male'), 'Male' => L10n::t('Male'),
'Female' => L10n::t('Female'), 'Female' => L10n::t('Female'),
'Currently Male' => L10n::t('Currently Male'), 'Currently Male' => L10n::t('Currently Male'),
@ -190,7 +190,7 @@ class ContactSelector
{ {
$o = ''; $o = '';
$select = [ $select = [
'EMPTY' => '', '' => L10n::t('No answer'),
'Males' => L10n::t('Males'), 'Males' => L10n::t('Males'),
'Females' => L10n::t('Females'), 'Females' => L10n::t('Females'),
'Gay' => L10n::t('Gay'), 'Gay' => L10n::t('Gay'),
@ -228,7 +228,7 @@ class ContactSelector
{ {
$o = ''; $o = '';
$select = [ $select = [
'EMPTY' => '', '' => L10n::t('No answer'),
'Single' => L10n::t('Single'), 'Single' => L10n::t('Single'),
'Lonely' => L10n::t('Lonely'), 'Lonely' => L10n::t('Lonely'),
'Available' => L10n::t('Available'), 'Available' => L10n::t('Available'),

View file

@ -67,6 +67,7 @@ class BBCode extends BaseObject
$post["after"] = trim(substr($body, $pos + strlen($data[0]))); $post["after"] = trim(substr($body, $pos + strlen($data[0])));
} else { } else {
$post["text"] = trim(str_replace($data[0], "", $body)); $post["text"] = trim(str_replace($data[0], "", $body));
$post["after"] = '';
} }
$attacheddata = $data[2]; $attacheddata = $data[2];
@ -1027,7 +1028,7 @@ class BBCode extends BaseObject
@curl_exec($ch); @curl_exec($ch);
$curl_info = @curl_getinfo($ch); $curl_info = @curl_getinfo($ch);
$a->saveTimestamp($stamp1, "network"); $a->getProfiler()->saveTimestamp($stamp1, "network", System::callstack());
if (substr($curl_info["content_type"], 0, 6) == "image/") { if (substr($curl_info["content_type"], 0, 6) == "image/") {
$text = "[url=" . $match[1] . "]" . $match[1] . "[/url]"; $text = "[url=" . $match[1] . "]" . $match[1] . "[/url]";
@ -1086,7 +1087,7 @@ class BBCode extends BaseObject
@curl_exec($ch); @curl_exec($ch);
$curl_info = @curl_getinfo($ch); $curl_info = @curl_getinfo($ch);
$a->saveTimestamp($stamp1, "network"); $a->getProfiler()->saveTimestamp($stamp1, "network", System::callstack());
// if its a link to a picture then embed this picture // if its a link to a picture then embed this picture
if (substr($curl_info["content_type"], 0, 6) == "image/") { if (substr($curl_info["content_type"], 0, 6) == "image/") {
@ -1915,7 +1916,7 @@ class BBCode extends BaseObject
// unmask the special chars back to HTML // unmask the special chars back to HTML
$text = str_replace(['&\_lt\_;', '&\_gt\_;', '&\_amp\_;'], ['&lt;', '&gt;', '&amp;'], $text); $text = str_replace(['&\_lt\_;', '&\_gt\_;', '&\_amp\_;'], ['&lt;', '&gt;', '&amp;'], $text);
$a->saveTimestamp($stamp1, "parser"); $a->getProfiler()->saveTimestamp($stamp1, "parser", System::callstack());
// Libertree has a problem with escaped hashtags. // Libertree has a problem with escaped hashtags.
$text = str_replace(['\#'], ['#'], $text); $text = str_replace(['\#'], ['#'], $text);

View file

@ -7,6 +7,7 @@
namespace Friendica\Content\Text; namespace Friendica\Content\Text;
use Friendica\BaseObject; use Friendica\BaseObject;
use Friendica\Core\System;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Michelf\MarkdownExtra; use Michelf\MarkdownExtra;
@ -36,7 +37,7 @@ class Markdown extends BaseObject
$html = $MarkdownParser->transform($text); $html = $MarkdownParser->transform($text);
$html = preg_replace('/<a(.*?)href="#/is', '<a$1href="' . ltrim($_SERVER['REQUEST_URI'], '/') . '#', $html); $html = preg_replace('/<a(.*?)href="#/is', '<a$1href="' . ltrim($_SERVER['REQUEST_URI'], '/') . '#', $html);
self::getApp()->saveTimestamp($stamp1, "parser"); self::getApp()->getProfiler()->saveTimestamp($stamp1, "parser", System::callstack());
return $html; return $html;
} }

View file

@ -219,7 +219,7 @@ class Addon extends BaseObject
$stamp1 = microtime(true); $stamp1 = microtime(true);
$f = file_get_contents("addon/$addon/$addon.php"); $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); $r = preg_match("|/\*.*\*/|msU", $f, $m);

View file

@ -63,7 +63,7 @@ class Cache extends \Friendica\BaseObject
$return = self::getDriver()->getAllKeys($prefix); $return = self::getDriver()->getAllKeys($prefix);
self::getApp()->saveTimestamp($time, 'cache'); self::getApp()->getProfiler()->saveTimestamp($time, 'cache', System::callstack());
return $return; return $return;
} }
@ -82,7 +82,7 @@ class Cache extends \Friendica\BaseObject
$return = self::getDriver()->get($key); $return = self::getDriver()->get($key);
self::getApp()->saveTimestamp($time, 'cache'); self::getApp()->getProfiler()->saveTimestamp($time, 'cache', System::callstack());
return $return; return $return;
} }
@ -105,7 +105,7 @@ class Cache extends \Friendica\BaseObject
$return = self::getDriver()->set($key, $value, $duration); $return = self::getDriver()->set($key, $value, $duration);
self::getApp()->saveTimestamp($time, 'cache_write'); self::getApp()->getProfiler()->saveTimestamp($time, 'cache_write', System::callstack());
return $return; return $return;
} }
@ -124,7 +124,7 @@ class Cache extends \Friendica\BaseObject
$return = self::getDriver()->delete($key); $return = self::getDriver()->delete($key);
self::getApp()->saveTimestamp($time, 'cache_write'); self::getApp()->getProfiler()->saveTimestamp($time, 'cache_write', System::callstack());
return $return; return $return;
} }

View file

@ -8,10 +8,6 @@
*/ */
namespace Friendica\Core; namespace Friendica\Core;
use Friendica\Core\Config\ConfigCache;
use Friendica\Core\Config\IConfigAdapter;
use Friendica\Core\Config\IConfigCache;
/** /**
* @brief Arbitrary system configuration storage * @brief Arbitrary system configuration storage
* *
@ -22,116 +18,76 @@ use Friendica\Core\Config\IConfigCache;
class Config class Config
{ {
/** /**
* @var Config\IConfigAdapter|null * @var Config\Configuration
*/ */
private static $adapter; private static $config;
/** /**
* @var Config\IConfigCache * Initialize the config
*/
private static $cache;
/**
* Initialize the config with only the cache
* *
* @param Config\IConfigCache $cache The configuration cache * @param Config\Configuration $config
*/ */
public static function init(Config\IConfigCache $cache) public static function init(Config\Configuration $config)
{ {
self::$cache = $cache; self::$config = $config;
}
/**
* Add the adapter for DB-backend
*
* @param Config\IConfigAdapter $adapter
*/
public static function setAdapter(Config\IConfigAdapter $adapter)
{
self::$adapter = $adapter;
} }
/** /**
* @brief Loads all configuration values of family into a cached storage. * @brief Loads all configuration values of family into a cached storage.
* *
* All configuration values of the system are stored in the cache ( @see IConfigCache ) * @param string $cat The category of the configuration value
*
* @param string $family The category of the configuration value
* *
* @return void * @return void
*/ */
public static function load($family = "config") public static function load($cat = "config")
{ {
if (!isset(self::$adapter) || !self::$adapter->isConnected()) { self::$config->load($cat);
return;
}
self::$adapter->load($family);
} }
/** /**
* @brief Get a particular user's config variable given the category name * @brief Get a particular user's config variable given the category name
* ($family) and a key. * ($family) and a key.
* *
* Get a particular config value from the given category ($family) * @param string $cat The category of the configuration value
* and the $key from a cached storage either from the self::$adapter
* (@see IConfigAdapter ) or from the static::$cache (@see IConfigCache ).
*
* @param string $family The category of the configuration value
* @param string $key The configuration key to query * @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 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) * @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 * @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)
{ {
if (!isset(self::$adapter) || !self::$adapter->isConnected()) { return self::$config->get($cat, $key, $default_value, $refresh);
return self::$cache->get($family, $key, $default_value);
}
return self::$adapter->get($family, $key, $default_value, $refresh);
} }
/** /**
* @brief Sets a configuration value for system config * @brief Sets a configuration value for system config
* *
* Stores a config value ($value) in the category ($family) under the key ($key) * 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! * 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 string $key The configuration key to set
* @param mixed $value The value to store * @param mixed $value The value to store
* *
* @return bool Operation success * @return bool Operation success
*/ */
public static function set($family, $key, $value) public static function set($cat, $key, $value)
{ {
if (!isset(self::$adapter) || !self::$adapter->isConnected()) { return self::$config->set($cat, $key, $value);
return self::$cache->set($family, $key, $value);
}
return self::$adapter->set($family, $key, $value);
} }
/** /**
* @brief Deletes the given key from the system configuration. * @brief Deletes the given key from the system configuration.
* *
* Removes the configured value from the stored cache in self::$config * @param string $cat The category of the configuration value
* (@see ConfigCache ) and removes it from the database (@see IConfigAdapter ).
*
* @param string $family The category of the configuration value
* @param string $key The configuration key to delete * @param string $key The configuration key to delete
* *
* @return mixed * @return bool
*/ */
public static function delete($family, $key) public static function delete($cat, $key)
{ {
if (!isset(self::$adapter) || !self::$adapter->isConnected()) { return self::$config->delete($cat, $key);
self::$cache->delete($family, $key);
}
return self::$adapter->delete($family, $key);
} }
} }

View file

@ -1,14 +0,0 @@
<?php
namespace Friendica\Core\Config;
abstract class AbstractDbaConfigAdapter
{
/** @var bool */
protected $connected = true;
public function isConnected()
{
return $this->connected;
}
}

View file

@ -0,0 +1,83 @@
<?php
namespace Friendica\Core\Config\Adapter;
use Friendica\Database\DBA;
abstract class AbstractDbaConfigAdapter
{
/**
* The connection state of the adapter
*
* @var bool
*/
protected $connected = true;
public function __construct()
{
$this->connected = DBA::connected();
}
/**
* Checks if the adapter is currently connected
*
* @return bool
*/
public function isConnected()
{
return $this->connected;
}
/**
* Formats a DB value to a config value
* - null = The db-value isn't set
* - bool = The db-value is either '0' or '1'
* - array = The db-value is a serialized array
* - string = The db-value is a string
*
* Keep in mind that there aren't any numeric/integer config values in the database
*
* @param null|string $value
*
* @return null|array|string
*/
protected function toConfigValue($value)
{
if (!isset($value)) {
return null;
}
switch (true) {
// manage array value
case preg_match("|^a:[0-9]+:{.*}$|s", $value):
return unserialize($value);
default:
return $value;
}
}
/**
* Formats a config value to a DB value (string)
*
* @param mixed $value
*
* @return string
*/
protected function toDbValue($value)
{
// if not set, save an empty string
if (!isset($value)) {
return '';
}
switch (true) {
// manage arrays
case is_array($value):
return serialize($value);
default:
return (string)$value;
}
}
}

View file

@ -0,0 +1,73 @@
<?php
namespace Friendica\Core\Config\Adapter;
/**
*
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
interface IConfigAdapter
{
/**
* Loads all configuration values and returns the loaded category as an array.
*
* @param string $cat The category of the configuration values to load
*
* @return array
*/
public function load($cat = "config");
/**
* Get a particular system-wide config variable given the category name
* ($family) and a key.
*
* Note: Boolean variables are defined as 0/1 in the database
*
* @param string $cat The category of the configuration value
* @param string $key The configuration key to query
*
* @return null|mixed Stored value or null if it does not exist
*/
public function get($cat, $key);
/**
* Stores a config value ($value) in the category ($family) 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
*/
public function set($cat, $key, $value);
/**
* Removes the configured value from the stored cache
* and removes it from the database.
*
* @param string $cat The category of the configuration value
* @param string $key The configuration key to delete
*
* @return bool Operation success
*/
public function delete($cat, $key);
/**
* Checks, if the current adapter is connected to the backend
*
* @return bool
*/
public function isConnected();
/**
* Checks, if a config key ($key) in the category ($cat) is already loaded.
*
* @param string $cat The configuration category
* @param string $key The configuration key
*
* @return bool
*/
public function isLoaded($cat, $key);
}

View file

@ -6,7 +6,7 @@
* and open the template in the editor. * and open the template in the editor.
*/ */
namespace Friendica\Core\Config; namespace Friendica\Core\Config\Adapter;
/** /**
* *
@ -15,12 +15,12 @@ namespace Friendica\Core\Config;
interface IPConfigAdapter interface IPConfigAdapter
{ {
/** /**
* Loads all configuration values of a user's config family into a cached storage. * Loads all configuration values of a user's config family and returns the loaded category as an array.
* *
* @param string $uid The user_id * @param string $uid The user_id
* @param string $cat The category of the configuration value * @param string $cat The category of the configuration value
* *
* @return void * @return array
*/ */
public function load($uid, $cat); public function load($uid, $cat);
@ -28,15 +28,15 @@ interface IPConfigAdapter
* Get a particular user's config variable given the category name * Get a particular user's config variable given the category name
* ($family) and a key. * ($family) and a key.
* *
* Note: Boolean variables are defined as 0/1 in the database
*
* @param string $uid The user_id * @param string $uid The user_id
* @param string $cat The category of the configuration value * @param string $cat The category of the configuration value
* @param string $k The configuration key to query * @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 * @return null|mixed Stored value or null if it does not exist
*/ */
public function get($uid, $cat, $k, $default_value = null, $refresh = false); public function get($uid, $cat, $key);
/** /**
* Stores a config value ($value) in the category ($family) under the key ($key) * Stores a config value ($value) in the category ($family) under the key ($key)
@ -46,12 +46,12 @@ interface IPConfigAdapter
* *
* @param string $uid The user_id * @param string $uid The user_id
* @param string $cat The category of the configuration value * @param string $cat The category of the configuration value
* @param string $k The configuration key to set * @param string $key The configuration key to set
* @param string $value The value to store * @param string $value The value to store
* *
* @return bool Operation success * @return bool Operation success
*/ */
public function set($uid, $cat, $k, $value); public function set($uid, $cat, $key, $value);
/** /**
* Removes the configured value from the stored cache * Removes the configured value from the stored cache
@ -59,9 +59,27 @@ interface IPConfigAdapter
* *
* @param string $uid The user_id * @param string $uid The user_id
* @param string $cat The category of the configuration value * @param string $cat The category of the configuration value
* @param string $k The configuration key to delete * @param string $key The configuration key to delete
* *
* @return mixed * @return bool Operation success
*/ */
public function delete($uid, $cat, $k); public function delete($uid, $cat, $key);
/**
* Checks, if the current adapter is connected to the backend
*
* @return bool
*/
public function isConnected();
/**
* Checks, if a config key ($key) in the category ($cat) is already loaded for the user_id $uid.
*
* @param string $uid The user_id
* @param string $cat The configuration category
* @param string $key The configuration key
*
* @return bool
*/
public function isLoaded($uid, $cat, $key);
} }

View file

@ -0,0 +1,145 @@
<?php
namespace Friendica\Core\Config\Adapter;
use Friendica\Database\DBA;
/**
* 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 AbstractDbaConfigAdapter implements IConfigAdapter
{
private $in_db;
/**
* {@inheritdoc}
*/
public function load($cat = "config")
{
$return = [];
if (!$this->isConnected()) {
return $return;
}
// We don't preload "system" anymore.
// This reduces the number of database reads a lot.
if ($cat === 'system') {
return $return;
}
$configs = DBA::select('config', ['v', 'k'], ['cat' => $cat]);
while ($config = DBA::fetch($configs)) {
$key = $config['k'];
$value = $this->toConfigValue($config['v']);
// The value was in the db, so don't check it again (unless you have to)
$this->in_db[$cat][$key] = true;
// just save it in case it is set
if (isset($value)) {
$return[$key] = $value;
}
}
DBA::close($configs);
return [$cat => $return];
}
/**
* {@inheritdoc}
*
* @param bool $mark if true, mark the selection of the current cat/key pair
*/
public function get($cat, $key, $mark = true)
{
if (!$this->isConnected()) {
return null;
}
// The value got checked, so mark it to avoid checking it over and over again
if ($mark) {
$this->in_db[$cat][$key] = true;
}
$config = DBA::selectFirst('config', ['v'], ['cat' => $cat, 'k' => $key]);
if (DBA::isResult($config)) {
$value = $this->toConfigValue($config['v']);
// just return it in case it is set
if (isset($value)) {
return $value;
}
}
return null;
}
/**
* {@inheritdoc}
*/
public function set($cat, $key, $value)
{
if (!$this->isConnected()) {
return false;
}
// 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.
$compare_value = (!is_array($value) ? (string)$value : $value);
$stored_value = $this->get($cat, $key, false);
if (!isset($this->in_db[$cat])) {
$this->in_db[$cat] = [];
}
if (!isset($this->in_db[$cat][$key])) {
$this->in_db[$cat][$key] = false;
}
if (isset($stored_value) && ($stored_value === $compare_value) && $this->in_db[$cat][$key]) {
return true;
}
$dbvalue = $this->toDbValue($value);
$result = DBA::update('config', ['v' => $dbvalue], ['cat' => $cat, 'k' => $key], true);
$this->in_db[$cat][$key] = $result;
return $result;
}
/**
* {@inheritdoc}
*/
public function delete($cat, $key)
{
if (!$this->isConnected()) {
return false;
}
if (isset($this->cache[$cat][$key])) {
unset($this->in_db[$cat][$key]);
}
$result = DBA::delete('config', ['cat' => $cat, 'k' => $key]);
return $result;
}
/**
* {@inheritdoc}
*/
public function isLoaded($cat, $key)
{
if (!$this->isConnected()) {
return false;
}
return (isset($this->in_db[$cat][$key])) && $this->in_db[$cat][$key];
}
}

View file

@ -0,0 +1,145 @@
<?php
namespace Friendica\Core\Config\Adapter;
use Friendica\Database\DBA;
/**
* 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 AbstractDbaConfigAdapter implements IPConfigAdapter
{
private $in_db;
/**
* {@inheritdoc}
*/
public function load($uid, $cat)
{
$return = [];
if (!$this->isConnected()) {
return $return;
}
$pconfigs = DBA::select('pconfig', ['v', 'k'], ['cat' => $cat, 'uid' => $uid]);
if (DBA::isResult($pconfigs)) {
while ($pconfig = DBA::fetch($pconfigs)) {
$key = $pconfig['k'];
$value = $this->toConfigValue($pconfig['v']);
// The value was in the db, so don't check it again (unless you have to)
$this->in_db[$uid][$cat][$key] = true;
if (isset($value)) {
$return[$key] = $value;
}
}
} else if ($cat != 'config') {
// Negative caching
$return = null;
}
DBA::close($pconfigs);
return [$cat => $return];
}
/**
* {@inheritdoc}
*
* @param bool $mark if true, mark the selection of the current cat/key pair
*/
public function get($uid, $cat, $key, $mark = true)
{
if (!$this->isConnected()) {
return null;
}
// The value was in the db, so don't check it again (unless you have to)
if ($mark) {
$this->in_db[$uid][$cat][$key] = true;
}
$pconfig = DBA::selectFirst('pconfig', ['v'], ['uid' => $uid, 'cat' => $cat, 'k' => $key]);
if (DBA::isResult($pconfig)) {
$value = $this->toConfigValue($pconfig['v']);
if (isset($value)) {
return $value;
}
}
$this->in_db[$uid][$cat][$key] = false;
return null;
}
/**
* {@inheritdoc}
*/
public function set($uid, $cat, $key, $value)
{
if (!$this->isConnected()) {
return false;
}
// 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.
$compare_value = (!is_array($value) ? (string)$value : $value);
$stored_value = $this->get($uid, $cat, $key, false);
if (!isset($this->in_db[$uid])) {
$this->in_db[$uid] = [];
}
if (!isset($this->in_db[$uid][$cat])) {
$this->in_db[$uid][$cat] = [];
}
if (!isset($this->in_db[$uid][$cat][$key])) {
$this->in_db[$uid][$cat][$key] = false;
}
if (isset($stored_value) && ($stored_value === $compare_value) && $this->in_db[$uid][$cat][$key]) {
return true;
}
// manage array value
$dbvalue = (is_array($value) ? serialize($value) : $value);
$result = DBA::update('pconfig', ['v' => $dbvalue], ['uid' => $uid, 'cat' => $cat, 'k' => $key], true);
$this->in_db[$uid][$cat][$key] = $result;
return $result;
}
/**
* {@inheritdoc}
*/
public function delete($uid, $cat, $key)
{
if (!$this->isConnected()) {
return false;
}
if (isset($this->in_db[$uid][$cat][$key])) {
unset($this->in_db[$uid][$cat][$key]);
}
return DBA::delete('pconfig', ['uid' => $uid, 'cat' => $cat, 'k' => $key]);
}
/**
* {@inheritdoc}
*/
public function isLoaded($uid, $cat, $key)
{
if (!$this->isConnected()) {
return false;
}
return (isset($this->in_db[$uid][$cat][$key])) && $this->in_db[$uid][$cat][$key];
}
}

View file

@ -0,0 +1,115 @@
<?php
namespace Friendica\Core\Config\Adapter;
use Friendica\Database\DBA;
/**
* 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 AbstractDbaConfigAdapter implements IConfigAdapter
{
private $config_loaded = false;
/**
* {@inheritdoc}
*/
public function load($cat = 'config')
{
$return = [];
if (!$this->isConnected()) {
return $return;
}
if ($this->config_loaded) {
return $return;
}
$configs = DBA::select('config', ['cat', 'v', 'k']);
while ($config = DBA::fetch($configs)) {
$value = $this->toConfigValue($config['v']);
if (isset($value)) {
$return[$config['cat']][$config['k']] = $value;
}
}
DBA::close($configs);
$this->config_loaded = true;
return $return;
}
/**
* {@inheritdoc}
*/
public function get($cat, $key)
{
if (!$this->isConnected()) {
return null;
}
$config = DBA::selectFirst('config', ['v'], ['cat' => $cat, 'k' => $key]);
if (DBA::isResult($config)) {
$value = $this->toConfigValue($config['v']);
if (isset($value)) {
return $value;
}
}
return null;
}
/**
* {@inheritdoc}
*/
public function set($cat, $key, $value)
{
if (!$this->isConnected()) {
return false;
}
// 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;
$stored_value = $this->get($cat, $key);
if (isset($stored_value) && $stored_value === $compare_value) {
return true;
}
$dbvalue = $this->toDbValue($value);
return DBA::update('config', ['v' => $dbvalue], ['cat' => $cat, 'k' => $key], true);
}
/**
* {@inheritdoc}
*/
public function delete($cat, $key)
{
if (!$this->isConnected()) {
return false;
}
return DBA::delete('config', ['cat' => $cat, 'k' => $key]);
}
/**
* {@inheritdoc}
*/
public function isLoaded($cat, $key)
{
if (!$this->isConnected()) {
return false;
}
return $this->config_loaded;
}
}

View file

@ -0,0 +1,142 @@
<?php
namespace Friendica\Core\Config\Adapter;
use Friendica\Database\DBA;
/**
* 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 AbstractDbaConfigAdapter implements IPConfigAdapter
{
/**
* @var array true if config for user is loaded
*/
private $config_loaded;
/**
* @param int $uid The UID of the current user
*/
public function __construct($uid = null)
{
parent::__construct();
$this->config_loaded = [];
if (isset($uid)) {
$this->load($uid, 'config');
}
}
/**
* {@inheritdoc}
*/
public function load($uid, $cat)
{
$return = [];
if (empty($uid)) {
return $return;
}
if (!$this->isLoaded($uid, $cat, null)) {
return $return;
}
$pconfigs = DBA::select('pconfig', ['cat', 'v', 'k'], ['uid' => $uid]);
while ($pconfig = DBA::fetch($pconfigs)) {
$value = $this->toConfigValue($pconfig['v']);
if (isset($value)) {
$return[$pconfig['cat']][$pconfig['k']] = $value;
}
}
DBA::close($pconfigs);
$this->config_loaded[$uid] = true;
return $return;
}
/**
* {@inheritdoc}
*/
public function get($uid, $cat, $key)
{
if (!$this->isConnected()) {
return null;
}
if (!$this->isLoaded($uid, $cat, $key)) {
$this->load($uid, $cat);
}
$config = DBA::selectFirst('pconfig', ['v'], ['uid' => $uid, 'cat' => $cat, 'k' => $key]);
if (DBA::isResult($config)) {
$value = $this->toConfigValue($config['v']);
if (isset($value)) {
return $value;
}
}
return null;
}
/**
* {@inheritdoc}
*/
public function set($uid, $cat, $key, $value)
{
if (!$this->isConnected()) {
return false;
}
if (!$this->isLoaded($uid, $cat, $key)) {
$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;
$stored_value = $this->get($uid, $cat, $key);
if (isset($stored_value) && $stored_value === $compare_value) {
return true;
}
$dbvalue = $this->toDbValue($value);
return DBA::update('pconfig', ['v' => $dbvalue], ['uid' => $uid, 'cat' => $cat, 'k' => $key], true);
}
/**
* {@inheritdoc}
*/
public function delete($uid, $cat, $key)
{
if (!$this->isConnected()) {
return false;
}
if (!$this->isLoaded($uid, $cat, $key)) {
$this->load($uid, $cat);
}
return DBA::delete('pconfig', ['uid' => $uid, 'cat' => $cat, 'k' => $key]);
}
/**
* {@inheritdoc}
*/
public function isLoaded($uid, $cat, $key)
{
if (!$this->isConnected()) {
return false;
}
return isset($this->config_loaded[$uid]) && $this->config_loaded[$uid];
}
}

View file

@ -0,0 +1,191 @@
<?php
namespace Friendica\Core\Config\Cache;
/**
* The Friendica config cache for the application
* Initial, all *.config.php files are loaded into this cache with the
* ConfigCacheLoader ( @see ConfigCacheLoader )
*/
class ConfigCache implements IConfigCache, IPConfigCache
{
/**
* @var array
*/
private $config;
/**
* @param array $config A initial config array
*/
public function __construct(array $config = [])
{
$this->load($config);
}
/**
* {@inheritdoc}
*/
public function load(array $config, $overwrite = false)
{
$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)) {
if ($overwrite) {
$this->set($category, $key, $value);
} else {
$this->setDefault($category, $key, $value);
}
}
}
}
}
}
/**
* {@inheritdoc}
*/
public function get($cat, $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 $k Config key
* @param mixed $v Default value to set
*/
private function setDefault($cat, $k, $v)
{
if (!isset($this->config[$cat][$k])) {
$this->set($cat, $k, $v);
}
}
/**
* {@inheritdoc}
*/
public function set($cat, $key, $value)
{
if (!isset($this->config[$cat])) {
$this->config[$cat] = [];
}
$this->config[$cat][$key] = $value;
return true;
}
/**
* {@inheritdoc}
*/
public function delete($cat, $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;
}
}
/**
* {@inheritdoc}
*/
public function loadP($uid, array $config)
{
$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->setP($uid, $category, $key, $value);
}
}
}
}
}
/**
* {@inheritdoc}
*/
public function getP($uid, $cat, $key = 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;
}
}
/**
* {@inheritdoc}
*/
public function setP($uid, $cat, $key, $value)
{
if (!isset($this->config[$uid]) || !is_array($this->config[$uid])) {
$this->config[$uid] = [];
}
if (!isset($this->config[$uid][$cat])) {
$this->config[$uid][$cat] = [];
}
$this->config[$uid][$cat][$key] = $value;
return true;
}
/**
* {@inheritdoc}
*/
public function deleteP($uid, $cat, $key)
{
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;
}
}

View file

@ -1,6 +1,6 @@
<?php <?php
namespace Friendica\Core\Config; namespace Friendica\Core\Config\Cache;
use Friendica\Core\Addon; use Friendica\Core\Addon;
@ -37,16 +37,13 @@ class ConfigCacheLoader
*/ */
public function loadConfigFiles(ConfigCache $config) public function loadConfigFiles(ConfigCache $config)
{ {
// Setting at least the basepath we know $config->load($this->loadCoreConfig('defaults'));
$config->set('system', 'basepath', $this->baseDir); $config->load($this->loadCoreConfig('settings'));
$config->loadConfigArray($this->loadCoreConfig('defaults')); $config->load($this->loadLegacyConfig('htpreconfig'), true);
$config->loadConfigArray($this->loadCoreConfig('settings')); $config->load($this->loadLegacyConfig('htconfig'), true);
$config->loadConfigArray($this->loadLegacyConfig('htpreconfig'), true); $config->load($this->loadCoreConfig('local'), true);
$config->loadConfigArray($this->loadLegacyConfig('htconfig'), true);
$config->loadConfigArray($this->loadCoreConfig('local'), true);
} }
/** /**

View file

@ -0,0 +1,56 @@
<?php
namespace Friendica\Core\Config\Cache;
/**
* The interface for a system-wide ConfigCache
*/
interface IConfigCache
{
/**
* 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
*/
function load(array $config, $overwrite = false);
/**
* 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
*/
function get($cat, $key = null);
/**
* 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
*/
function set($cat, $key, $value);
/**
* Deletes a value from the config cache.
*
* @param string $cat Config category
* @param string $key Config key
*
* @return bool true, if deleted
*/
function delete($cat, $key);
/**
* Returns the whole configuration cache
*
* @return array
*/
function getAll();
}

View file

@ -1,23 +1,31 @@
<?php <?php
namespace Friendica\Core\Config; namespace Friendica\Core\Config\Cache;
/** /**
* The interface for a user-specific config cache * The interface for a user-specific config cache
*/ */
interface IPConfigCache interface IPConfigCache
{ {
/**
* 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
*/
function loadP($uid, array $config);
/** /**
* Retrieves a value from the user config cache * Retrieves a value from the user config cache
* *
* @param int $uid User Id * @param int $uid User Id
* @param string $cat Config category * @param string $cat Config category
* @param string $key Config key * @param string $key Config key
* @param mixed $default Default value if key isn't set
* *
* @return string The value of the config entry * @return null|string The value of the config entry or null if not set
*/ */
function getP($uid, $cat, $key = null, $default = null); function getP($uid, $cat, $key = null);
/** /**
* Sets a value in the user config cache * Sets a value in the user config cache
@ -37,8 +45,15 @@ interface IPConfigCache
* @param int $uid User Id * @param int $uid User Id
* @param string $cat Config category * @param string $cat Config category
* @param string $key Config key * @param string $key Config key
*
* @return bool true, if deleted
*/ */
function deleteP($uid, $cat, $key); function deleteP($uid, $cat, $key);
/**
* Returns the whole configuration cache
*
* @return array
*/
function getAll(); function getAll();
} }

View file

@ -1,184 +0,0 @@
<?php
namespace Friendica\Core\Config;
/**
* The Friendica config cache for the application
* Initial, all *.config.php files are loaded into this cache with the
* ConfigCacheLoader ( @see ConfigCacheLoader )
*
* Is used for further caching operations too (depending on the ConfigAdapter )
*/
class ConfigCache implements IConfigCache, IPConfigCache
{
private $config;
/**
* @param array $config A initial config array
*/
public function __construct(array $config = [])
{
$this->loadConfigArray($config);
}
/**
* Tries to load the specified configuration array into the App->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 loadConfigArray(array $config, $overwrite = false)
{
foreach ($config as $category => $values) {
foreach ($values as $key => $value) {
if ($overwrite) {
$this->set($category, $key, $value);
} else {
$this->setDefault($category, $key, $value);
}
}
}
}
/**
* {@inheritdoc}
*/
public function get($cat, $key = null, $default = null)
{
$return = $default;
if ($cat === 'config') {
if (isset($this->config[$key])) {
$return = $this->config[$key];
}
} else {
if (isset($this->config[$cat][$key])) {
$return = $this->config[$cat][$key];
} elseif ($key == null && isset($this->config[$cat])) {
$return = $this->config[$cat];
}
}
return $return;
}
/**
* Sets a default value in the config cache. Ignores already existing keys.
*
* @param string $cat Config category
* @param string $k Config key
* @param mixed $v Default value to set
*/
private function setDefault($cat, $k, $v)
{
if (!isset($this->config[$cat][$k])) {
$this->set($cat, $k, $v);
}
}
/**
* {@inheritdoc}
*/
public function set($cat, $key, $value)
{
// Only arrays are serialized in database, so we have to unserialize sparingly
$value = is_string($value) && preg_match("|^a:[0-9]+:{.*}$|s", $value) ? unserialize($value) : $value;
if ($cat === 'config') {
$this->config[$key] = $value;
} else {
if (!isset($this->config[$cat])) {
$this->config[$cat] = [];
}
$this->config[$cat][$key] = $value;
}
return true;
}
/**
* {@inheritdoc}
*/
public function delete($cat, $key)
{
if ($cat === 'config') {
if (isset($this->config[$key])) {
unset($this->config[$key]);
}
} else {
if (isset($this->config[$cat][$key])) {
unset($this->config[$cat][$key]);
if (count($this->config[$cat]) == 0) {
unset($this->config[$cat]);
}
}
}
}
/**
* {@inheritdoc}
*/
public function getP($uid, $cat, $key = null, $default = null)
{
$return = $default;
if (isset($this->config[$uid][$cat][$key])) {
$return = $this->config[$uid][$cat][$key];
} elseif ($key === null && isset($this->config[$uid][$cat])) {
$return = $this->config[$uid][$cat];
}
return $return;
}
/**
* {@inheritdoc}
*/
public function setP($uid, $cat, $key, $value)
{
// Only arrays are serialized in database, so we have to unserialize sparingly
$value = is_string($value) && preg_match("|^a:[0-9]+:{.*}$|s", $value) ? unserialize($value) : $value;
if (!isset($this->config[$uid]) || !is_array($this->config[$uid])) {
$this->config[$uid] = [];
}
if (!isset($this->config[$uid][$cat]) || !is_array($this->config[$uid][$cat])) {
$this->config[$uid][$cat] = [];
}
if ($key === null) {
$this->config[$uid][$cat] = $value;
} else {
$this->config[$uid][$cat][$key] = $value;
}
}
/**
* {@inheritdoc}
*/
public function deleteP($uid, $cat, $key)
{
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]);
}
}
}
}
/**
* Returns the whole configuration
*
* @return array The configuration
*/
public function getAll()
{
return $this->config;
}
}

View file

@ -0,0 +1,152 @@
<?php
namespace Friendica\Core\Config;
/**
* 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\IConfigCache )
* - The Config-DB-Table (per Config-DB-adapter @see Adapter\IConfigAdapter )
*/
class Configuration
{
/**
* @var Cache\IConfigCache
*/
private $configCache;
/**
* @var Adapter\IConfigAdapter
*/
private $configAdapter;
/**
* @param Cache\IConfigCache $configCache The configuration cache (based on the config-files)
* @param Adapter\IConfigAdapter $configAdapter The configuration DB-backend
*/
public function __construct(Cache\IConfigCache $configCache, Adapter\IConfigAdapter $configAdapter)
{
$this->configCache = $configCache;
$this->configAdapter = $configAdapter;
$this->load();
}
/**
* Returns the Config Cache
*
* @return Cache\IConfigCache
*/
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 IConfigCache )
*
* @param string $cat The category of the configuration value
*
* @return void
*/
public function load($cat = 'config')
{
// If not connected, do nothing
if (!$this->configAdapter->isConnected()) {
return;
}
// load the whole category out of the DB into the cache
$this->configCache->load($this->configAdapter->load($cat), true);
}
/**
* @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 IConfigCache ).
*
* @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 function get($cat, $key, $default_value = null, $refresh = false)
{
// if the value isn't loaded or refresh is needed, load it to the cache
if ($this->configAdapter->isConnected() &&
(!$this->configAdapter->isLoaded($cat, $key) ||
$refresh)) {
$dbvalue = $this->configAdapter->get($cat, $key);
if (isset($dbvalue)) {
$this->configCache->set($cat, $key, $dbvalue);
return $dbvalue;
}
}
// use the config cache for return
$result = $this->configCache->get($cat, $key);
return (isset($result)) ? $result : $default_value;
}
/**
* @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
*/
public function set($cat, $key, $value)
{
// set the cache first
$cached = $this->configCache->set($cat, $key, $value);
// If there is no connected adapter, we're finished
if (!$this->configAdapter->isConnected()) {
return $cached;
}
$stored = $this->configAdapter->set($cat, $key, $value);
return $cached && $stored;
}
/**
* @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
*/
public function delete($cat, $key)
{
$cacheRemoved = $this->configCache->delete($cat, $key);
if (!$this->configAdapter->isConnected()) {
return $cacheRemoved;
}
$storeRemoved = $this->configAdapter->delete($cat, $key);
return $cacheRemoved || $storeRemoved;
}
}

View file

@ -1,64 +0,0 @@
<?php
namespace Friendica\Core\Config;
/**
*
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
interface IConfigAdapter
{
/**
* Loads all configuration values into a cached storage.
*
* @param string $cat The category of the configuration values to load
*
* @return void
*/
public function load($cat = "config");
/**
* Get a particular user's config variable given the category name
* ($family) and a key.
*
* @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);
/**
* 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 $cat The category of the configuration value
* @param string $k The configuration key to set
* @param mixed $value The value to store
*
* @return bool Operation success
*/
public function set($cat, $k, $value);
/**
* Removes the configured value from the stored cache
* 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);
/**
* Checks, if the current adapter is connected to the backend
*
* @return bool
*/
public function isConnected();
}

View file

@ -1,39 +0,0 @@
<?php
namespace Friendica\Core\Config;
/**
* The interface for a system-wide ConfigCache
*/
interface IConfigCache
{
/**
* @param string $cat Config category
* @param string $key Config key
* @param mixed $default Default value if it isn't set
*
* @return mixed Returns the value of the Config entry
*/
function get($cat, $key = null, $default = null);
/**
* 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
*/
function set($cat, $key, $value);
/**
* Deletes a value from the config cache
*
* @param string $cat Config category
* @param string $key Config key
*/
function delete($cat, $key);
function getAll();
}

View file

@ -1,172 +0,0 @@
<?php
namespace Friendica\Core\Config;
use Friendica\Database\DBA;
/**
* 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 AbstractDbaConfigAdapter implements IConfigAdapter
{
private $cache;
private $in_db;
/**
* @var IConfigCache The config cache of this driver
*/
private $configCache;
/**
* @param IConfigCache $configCache The config cache of this driver
*/
public function __construct(IConfigCache $configCache)
{
$this->configCache = $configCache;
$this->connected = DBA::connected();
}
/**
* {@inheritdoc}
*/
public function load($cat = "config")
{
if (!$this->isConnected()) {
return;
}
// 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'];
$this->configCache->set($cat, $k, $config['v']);
if ($cat !== 'config') {
$this->cache[$cat][$k] = $config['v'];
$this->in_db[$cat][$k] = true;
}
}
DBA::close($configs);
}
/**
* {@inheritdoc}
*/
public function get($cat, $k, $default_value = null, $refresh = false)
{
if (!$this->isConnected()) {
return $default_value;
}
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 ($this->configCache->get($cat, $k) !== null) {
// Assign the value (mostly) from config/local.config.php file to the cache
$this->cache[$cat][$k] = $this->configCache->get($cat, $k);
$this->in_db[$cat][$k] = false;
return $this->configCache->get($cat, $k);
} elseif ($this->configCache->get('config', $k) !== null) {
// Assign the value (mostly) from config/local.config.php file to the cache
$this->cache[$k] = $this->configCache->get('config', $k);
$this->in_db[$k] = false;
return $this->configCache->get('config', $k);
}
$this->cache[$cat][$k] = '!<unset>!';
$this->in_db[$cat][$k] = false;
return $default_value;
}
/**
* {@inheritdoc}
*/
public function set($cat, $k, $value)
{
if (!$this->isConnected()) {
return false;
}
// 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;
}
$this->configCache->set($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;
}
/**
* {@inheritdoc}
*/
public function delete($cat, $k)
{
if (!$this->isConnected()) {
return false;
}
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,136 +0,0 @@
<?php
namespace Friendica\Core\Config;
use Friendica\Database\DBA;
/**
* 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 implements IPConfigAdapter
{
private $in_db;
/**
* The config cache of this adapter
* @var IPConfigCache
*/
private $configCache;
/**
* @param IPConfigCache $configCache The config cache of this adapter
*/
public function __construct(IPConfigCache $configCache)
{
$this->configCache = $configCache;
}
/**
* {@inheritdoc}
*/
public function load($uid, $cat)
{
$pconfigs = DBA::select('pconfig', ['v', 'k'], ['cat' => $cat, 'uid' => $uid]);
if (DBA::isResult($pconfigs)) {
while ($pconfig = DBA::fetch($pconfigs)) {
$k = $pconfig['k'];
$this->configCache->setP($uid, $cat, $k, $pconfig['v']);
$this->in_db[$uid][$cat][$k] = true;
}
} else if ($cat != 'config') {
// Negative caching
$this->configCache->setP($uid, $cat, null, "!<unset>!");
}
DBA::close($pconfigs);
}
/**
* {@inheritdoc}
*/
public function get($uid, $cat, $k, $default_value = null, $refresh = false)
{
if (!$refresh) {
// Looking if the whole family isn't set
if ($this->configCache->getP($uid, $cat) !== null) {
if ($this->configCache->getP($uid, $cat) === '!<unset>!') {
return $default_value;
}
}
if ($this->configCache->getP($uid, $cat, $k) !== null) {
if ($this->configCache->getP($uid, $cat, $k) === '!<unset>!') {
return $default_value;
}
return $this->configCache->getP($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']);
$this->configCache->setP($uid, $cat, $k, $val);
$this->in_db[$uid][$cat][$k] = true;
return $val;
} else {
$this->configCache->setP($uid, $cat, $k, '!<unset>!');
$this->in_db[$uid][$cat][$k] = false;
return $default_value;
}
}
/**
* {@inheritdoc}
*/
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;
}
$this->configCache->setP($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;
}
/**
* {@inheritdoc}
*/
public function delete($uid, $cat, $k)
{
$this->configCache->deleteP($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,146 @@
<?php
namespace Friendica\Core\Config;
/**
* This class is responsible for the user-specific configuration values in Friendica
* The values are set through the Config-DB-Table (per Config-DB-adapter @see Adapter\IPConfigAdapter )
*
* The configuration cache (@see Cache\IPConfigCache ) is used for temporary caching of database calls. This will
* increase the performance.
*/
class PConfiguration
{
/**
* @var Cache\IPConfigCache
*/
private $configCache;
/**
* @var Adapter\IPConfigAdapter
*/
private $configAdapter;
/**
* @param Cache\IPConfigCache $configCache The configuration cache
* @param Adapter\IPConfigAdapter $configAdapter The configuration DB-backend
*/
public function __construct(Cache\IPConfigCache $configCache, Adapter\IPConfigAdapter $configAdapter)
{
$this->configCache = $configCache;
$this->configAdapter = $configAdapter;
}
/**
* @brief 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 ( @see IPConfigCache )
*
* @param string $uid The user_id
* @param string $cat The category of the configuration value
*
* @return void
*/
public function load($uid, $cat = 'config')
{
// If not connected, do nothing
if (!$this->configAdapter->isConnected()) {
return;
}
// load the whole category out of the DB into the cache
$this->configCache->loadP($uid, $this->configAdapter->load($uid, $cat));
}
/**
* @brief 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 IConfigCache ).
*
* @param string $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
*/
public function get($uid, $cat, $key, $default_value = null, $refresh = false)
{
// if the value isn't loaded or refresh is needed, load it to the cache
if ($this->configAdapter->isConnected() &&
(!$this->configAdapter->isLoaded($uid, $cat, $key) ||
$refresh)) {
$dbValue = $this->configAdapter->get($uid, $cat, $key);
if (isset($dbValue)) {
$this->configCache->setP($uid, $cat, $key, $dbValue);
return $dbValue;
}
}
// use the config cache for return
$result = $this->configCache->getP($uid, $cat, $key);
return (isset($result)) ? $result : $default_value;
}
/**
* @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 $key The configuration key to set
* @param mixed $value The value to store
*
* @return bool Operation success
*/
public function set($uid, $cat, $key, $value)
{
// set the cache first
$cached = $this->configCache->setP($uid, $cat, $key, $value);
// If there is no connected adapter, we're finished
if (!$this->configAdapter->isConnected()) {
return $cached;
}
$stored = $this->configAdapter->set($uid, $cat, $key, $value);
return $cached && $stored;
}
/**
* @brief 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 string $uid The user_id
* @param string $cat The category of the configuration value
* @param string $key The configuration key to delete
*
* @return bool
*/
public function delete($uid, $cat, $key)
{
$cacheRemoved = $this->configCache->deleteP($uid, $cat, $key);
if (!$this->configAdapter->isConnected()) {
return $cacheRemoved;
}
$storeRemoved = $this->configAdapter->delete($uid, $cat, $key);
return $cacheRemoved || $storeRemoved;
}
}

View file

@ -1,123 +0,0 @@
<?php
namespace Friendica\Core\Config;
use Exception;
use Friendica\Database\DBA;
/**
* 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 AbstractDbaConfigAdapter implements IConfigAdapter
{
private $config_loaded = false;
/**
* @var IConfigCache The config cache of this driver
*/
private $configCache;
/**
* @param IConfigCache $configCache The config cache of this driver
*/
public function __construct(IConfigCache $configCache)
{
$this->configCache = $configCache;
$this->connected = DBA::connected();
$this->load();
}
/**
* {@inheritdoc}
*/
public function load($family = 'config')
{
if (!$this->isConnected()) {
return;
}
if ($this->config_loaded) {
return;
}
$configs = DBA::select('config', ['cat', 'v', 'k']);
while ($config = DBA::fetch($configs)) {
$this->configCache->set($config['cat'], $config['k'], $config['v']);
}
DBA::close($configs);
$this->config_loaded = true;
}
/**
* {@inheritdoc}
*/
public function get($cat, $k, $default_value = null, $refresh = false)
{
if (!$this->isConnected()) {
return $default_value;
}
if ($refresh) {
$config = DBA::selectFirst('config', ['v'], ['cat' => $cat, 'k' => $k]);
if (DBA::isResult($config)) {
$this->configCache->set($cat, $k, $config['v']);
}
}
$return = $this->configCache->get($cat, $k, $default_value);
return $return;
}
/**
* {@inheritdoc}
*/
public function set($cat, $k, $value)
{
if (!$this->isConnected()) {
return false;
}
// 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 ($this->configCache->get($cat, $k) === $compare_value) {
return true;
}
$this->configCache->set($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;
}
/**
* {@inheritdoc}
*/
public function delete($cat, $k)
{
if (!$this->isConnected()) {
return false;
}
$this->configCache->delete($cat, $k);
$result = DBA::delete('config', ['cat' => $cat, 'k' => $k]);
return $result;
}
}

View file

@ -1,125 +0,0 @@
<?php
namespace Friendica\Core\Config;
use Exception;
use Friendica\Database\DBA;
/**
* 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 implements IPConfigAdapter
{
private $config_loaded = false;
/**
* The config cache of this adapter
* @var IPConfigCache
*/
private $configCache;
/**
* @param IPConfigCache $configCache The config cache of this adapter
* @param int $uid The UID of the current user
*/
public function __construct(IPConfigCache $configCache, $uid = null)
{
$this->configCache = $configCache;
if (isset($uid)) {
$this->load($uid, 'config');
}
}
/**
* {@inheritdoc}
*/
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)) {
$this->configCache->setP($uid, $pconfig['cat'], $pconfig['k'], $pconfig['v']);
}
DBA::close($pconfigs);
$this->config_loaded = true;
}
/**
* {@inheritdoc}
*/
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)) {
$this->configCache->setP($uid, $cat, $k, $config['v']);
} else {
$this->configCache->deleteP($uid, $cat, $k);
}
}
return $this->configCache->getP($uid, $cat, $k, $default_value);;
}
/**
* {@inheritdoc}
*/
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 ($this->configCache->getP($uid, $cat, $k) === $compare_value) {
return true;
}
$this->configCache->setP($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;
}
/**
* {@inheritdoc}
*/
public function delete($uid, $cat, $k)
{
if (!$this->config_loaded) {
$this->load($uid, $cat);
}
$this->configCache->deleteP($uid, $cat, $k);
$result = DBA::delete('pconfig', ['uid' => $uid, 'cat' => $cat, 'k' => $k]);
return $result;
}
}

View file

@ -100,10 +100,10 @@ HELP;
} }
} }
$db_host = $a->getConfig()->get('database', 'hostname'); $db_host = $a->getConfigCache()->get('database', 'hostname');
$db_user = $a->getConfig()->get('database', 'username'); $db_user = $a->getConfigCache()->get('database', 'username');
$db_pass = $a->getConfig()->get('database', 'password'); $db_pass = $a->getConfigCache()->get('database', 'password');
$db_data = $a->getConfig()->get('database', 'database'); $db_data = $a->getConfigCache()->get('database', 'database');
} else { } else {
// Creating config file // Creating config file
$this->out("Creating config file...\n"); $this->out("Creating config file...\n");
@ -146,7 +146,7 @@ HELP;
$installer->resetChecks(); $installer->resetChecks();
if (!$installer->checkDB($a->getConfig(), $db_host, $db_user, $db_pass, $db_data)) { if (!$installer->checkDB($a->getBasePath(), $a->getConfigCache(), $a->getProfiler(), $db_host, $db_user, $db_pass, $db_data)) {
$errorMessage = $this->extractErrors($installer->getChecks()); $errorMessage = $this->extractErrors($installer->getChecks());
throw new RuntimeException($errorMessage); throw new RuntimeException($errorMessage);
} }

View file

@ -124,9 +124,9 @@ HELP;
$cat = $this->getArgument(0); $cat = $this->getArgument(0);
Core\Config::load($cat); Core\Config::load($cat);
if ($a->getConfig()->get($cat) !== null) { if ($a->getConfigCache()->get($cat) !== null) {
$this->out("[{$cat}]"); $this->out("[{$cat}]");
$catVal = $a->getConfig()->get($cat); $catVal = $a->getConfigCache()->get($cat);
foreach ($catVal as $key => $value) { foreach ($catVal as $key => $value) {
if (is_array($value)) { if (is_array($value)) {
foreach ($value as $k => $v) { foreach ($value as $k => $v) {
@ -148,7 +148,7 @@ HELP;
$this->out('Warning: The JIT (Just In Time) Config adapter doesn\'t support loading the entire configuration, showing file config only'); $this->out('Warning: The JIT (Just In Time) Config adapter doesn\'t support loading the entire configuration, showing file config only');
} }
$config = $a->getConfig()->getAll(); $config = $a->getConfigCache()->getAll();
foreach ($config as $cat => $section) { foreach ($config as $cat => $section) {
if (is_array($section)) { if (is_array($section)) {
foreach ($section as $key => $value) { foreach ($section as $key => $value) {

View file

@ -31,9 +31,10 @@ Commands
toinnodb Convert all tables from MyISAM to InnoDB toinnodb Convert all tables from MyISAM to InnoDB
Options Options
-h|--help|-? Show help information -h|--help|-? Show help information
-v Show more debug information. -v Show more debug information.
-f|--force Force the command in case of "update" (Ignore failed updates/running updates) -f|--force Force the update command (Even if the database structure matches)
-o|--override Override running or stalling updates
HELP; HELP;
return $help; return $help;
} }
@ -68,8 +69,9 @@ HELP;
$output = DBStructure::update($a->getBasePath(), true, false); $output = DBStructure::update($a->getBasePath(), true, false);
break; break;
case "update": case "update":
$force = $this->getOption(['f', 'force'], false); $force = $this->getOption(['f', 'force'], false);
$output = Update::run($a->getBasePath(), $force, true, false); $override = $this->getOption(['o', 'override'], false);
$output = Update::run($a->getBasePath(), $force, $override,true, false);
break; break;
case "dumpsql": case "dumpsql":
ob_start(); ob_start();
@ -89,5 +91,4 @@ HELP;
return 0; return 0;
} }
} }

View file

@ -56,7 +56,7 @@ HELP;
} }
echo L10n::t('Check for pending update actions.') . "\n"; echo L10n::t('Check for pending update actions.') . "\n";
Update::run($a->getBasePath(), true, true, false); Update::run($a->getBasePath(), true, false, true, false);
echo L10n::t('Done.') . "\n"; echo L10n::t('Done.') . "\n";
echo L10n::t('Execute pending post updates.') . "\n"; echo L10n::t('Execute pending post updates.') . "\n";

View file

@ -43,7 +43,7 @@ HELP;
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments'); throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
} }
$php_path = BaseObject::getApp()->getConfig()->get('config', 'php_path', 'php'); $php_path = BaseObject::getApp()->getConfigCache()->get('config', 'php_path', 'php');
if ($this->getOption('v')) { if ($this->getOption('v')) {
$this->out('Directory: src'); $this->out('Directory: src');

View file

@ -6,11 +6,12 @@ namespace Friendica\Core;
use DOMDocument; use DOMDocument;
use Exception; use Exception;
use Friendica\Core\Config\ConfigCache; use Friendica\Core\Config\Cache\IConfigCache;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\Database\DBStructure; use Friendica\Database\DBStructure;
use Friendica\Object\Image; use Friendica\Object\Image;
use Friendica\Util\Network; use Friendica\Util\Network;
use Friendica\Util\Profiler;
use Friendica\Util\Strings; use Friendica\Util\Strings;
/** /**
@ -357,6 +358,7 @@ class Installer
* - mb_string * - mb_string
* - XML * - XML
* - iconv * - iconv
* - fileinfo
* - POSIX * - POSIX
* *
* @return bool false if something required failed * @return bool false if something required failed
@ -452,6 +454,13 @@ class Installer
); );
$returnVal = $returnVal ? $status : false; $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; return $returnVal;
} }
@ -582,7 +591,9 @@ class Installer
/** /**
* Checking the Database connection and if it is available for the current installation * Checking the Database connection and if it is available for the current installation
* *
* @param ConfigCache $configCache The configuration cache * @param string $basePath The basepath of this call
* @param IConfigCache $configCache The configuration cache
* @param Profiler $profiler The profiler of this app
* @param string $dbhost Hostname/IP of the Friendica Database * @param string $dbhost Hostname/IP of the Friendica Database
* @param string $dbuser Username of the Database connection credentials * @param string $dbuser Username of the Database connection credentials
* @param string $dbpass Password of the Database connection credentials * @param string $dbpass Password of the Database connection credentials
@ -591,9 +602,9 @@ class Installer
* @return bool true if the check was successful, otherwise false * @return bool true if the check was successful, otherwise false
* @throws Exception * @throws Exception
*/ */
public function checkDB(ConfigCache $configCache, $dbhost, $dbuser, $dbpass, $dbdata) public function checkDB($basePath, IConfigCache $configCache, Profiler $profiler, $dbhost, $dbuser, $dbpass, $dbdata)
{ {
if (!DBA::connect($configCache, $dbhost, $dbuser, $dbpass, $dbdata)) { if (!DBA::connect($basePath, $configCache, $profiler, $dbhost, $dbuser, $dbpass, $dbdata)) {
$this->addCheck(L10n::t('Could not connect to database.'), false, true, ''); $this->addCheck(L10n::t('Could not connect to database.'), false, true, '');
return false; return false;

View file

@ -122,12 +122,13 @@ class Lock
/** /**
* @brief Releases a lock if it was set by us * @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 * @return void
*/ */
public static function release($key) public static function release($key, $override = false)
{ {
self::getDriver()->releaseLock($key); self::getDriver()->releaseLock($key, $override);
} }
/** /**

View file

@ -61,11 +61,15 @@ class CacheLockDriver extends AbstractLockDriver
/** /**
* (@inheritdoc) * (@inheritdoc)
*/ */
public function releaseLock($key) public function releaseLock($key, $override = false)
{ {
$cachekey = self::getLockKey($key); $cachekey = self::getLockKey($key);
$this->cache->compareDelete($cachekey, getmypid()); if ($override) {
$this->cache->delete($cachekey);
} else {
$this->cache->compareDelete($cachekey, getmypid());
}
$this->markRelease($key); $this->markRelease($key);
} }

View file

@ -68,9 +68,15 @@ class DatabaseLockDriver extends AbstractLockDriver
/** /**
* (@inheritdoc) * (@inheritdoc)
*/ */
public function releaseLock($key) public function releaseLock($key, $override = false)
{ {
DBA::delete('locks', ['name' => $key, 'pid' => $this->pid]); if ($override) {
$where = ['name' => $key];
} else {
$where = ['name' => $key, 'pid' => $this->pid];
}
DBA::delete('locks', $where);
$this->markRelease($key); $this->markRelease($key);

View file

@ -33,11 +33,12 @@ interface ILockDriver
/** /**
* Releases a lock if it was set by us * Releases a lock if it was set by us
* *
* @param string $key The Name of the lock * @param string $key The Name of the lock
* @param bool $override Overrides the lock to get released
* *
* @return void * @return void
*/ */
public function releaseLock($key); public function releaseLock($key, $override = false);
/** /**
* Releases all lock that were set by us * Releases all lock that were set by us

View file

@ -50,7 +50,7 @@ class SemaphoreLockDriver extends AbstractLockDriver
/** /**
* (@inheritdoc) * (@inheritdoc)
*/ */
public function releaseLock($key) public function releaseLock($key, $override = false)
{ {
if (empty(self::$semaphore[$key])) { if (empty(self::$semaphore[$key])) {
return false; return false;

View file

@ -5,8 +5,6 @@
namespace Friendica\Core; namespace Friendica\Core;
use Friendica\BaseObject; use Friendica\BaseObject;
use Friendica\Factory\LoggerFactory;
use Friendica\Network\HTTPException\InternalServerErrorException;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel; use Psr\Log\LogLevel;
@ -67,73 +65,22 @@ class Logger extends BaseObject
/** /**
* Sets the default logging handler for Friendica. * Sets the default logging handler for Friendica.
* @todo Can be combined with other handlers too if necessary, could be configurable.
* *
* @param LoggerInterface $logger The Logger instance of this Application * @param LoggerInterface $logger The Logger instance of this Application
*
* @throws InternalServerErrorException if the logger factory is incompatible to this logger
*/ */
public static function setLogger($logger) public static function init(LoggerInterface $logger)
{ {
$debugging = Config::get('system', 'debugging');
$logfile = Config::get('system', 'logfile');
$loglevel = Config::get('system', 'loglevel');
if (!$debugging || !$logfile) {
return;
}
$loglevel = self::mapLegacyConfigDebugLevel((string)$loglevel);
LoggerFactory::addStreamHandler($logger, $logfile, $loglevel);
self::$logger = $logger; self::$logger = $logger;
$logfile = Config::get('system', 'dlogfile');
if (!$logfile) {
return;
}
$developIp = Config::get('system', 'dlogip');
self::$devLogger = LoggerFactory::createDev('develop', $developIp);
LoggerFactory::addStreamHandler(self::$devLogger, $logfile, LogLevel::DEBUG);
} }
/** /**
* Mapping a legacy level to the PSR-3 compliant levels * Sets the default dev-logging handler for Friendica.
* @see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md#5-psrlogloglevel
* *
* @param string $level the level to be mapped * @param LoggerInterface $logger The Logger instance of this Application
*
* @return string the PSR-3 compliant level
*/ */
private static function mapLegacyConfigDebugLevel($level) public static function setDevLogger(LoggerInterface $logger)
{ {
switch ($level) { self::$devLogger = $logger;
// legacy WARNING
case "0":
return LogLevel::ERROR;
// legacy INFO
case "1":
return LogLevel::WARNING;
// legacy TRACE
case "2":
return LogLevel::NOTICE;
// legacy DEBUG
case "3":
return LogLevel::INFO;
// legacy DATA
case "4":
return LogLevel::DEBUG;
// legacy ALL
case "5":
return LogLevel::DEBUG;
// default if nothing set
default:
return $level;
}
} }
/** /**
@ -155,7 +102,7 @@ class Logger extends BaseObject
$stamp1 = microtime(true); $stamp1 = microtime(true);
self::$logger->emergency($message, $context); self::$logger->emergency($message, $context);
self::getApp()->saveTimestamp($stamp1, 'file'); self::getApp()->GetProfiler()->saveTimestamp($stamp1, 'file', System::callstack());
} }
/** /**
@ -179,7 +126,7 @@ class Logger extends BaseObject
$stamp1 = microtime(true); $stamp1 = microtime(true);
self::$logger->alert($message, $context); self::$logger->alert($message, $context);
self::getApp()->saveTimestamp($stamp1, 'file'); self::getApp()->getProfiler()->saveTimestamp($stamp1, 'file', System::callstack());
} }
/** /**
@ -202,7 +149,7 @@ class Logger extends BaseObject
$stamp1 = microtime(true); $stamp1 = microtime(true);
self::$logger->critical($message, $context); self::$logger->critical($message, $context);
self::getApp()->saveTimestamp($stamp1, 'file'); self::getApp()->getProfiler()->saveTimestamp($stamp1, 'file', System::callstack());
} }
/** /**
@ -225,7 +172,7 @@ class Logger extends BaseObject
$stamp1 = microtime(true); $stamp1 = microtime(true);
self::$logger->error($message, $context); self::$logger->error($message, $context);
self::getApp()->saveTimestamp($stamp1, 'file'); self::getApp()->getProfiler()->saveTimestamp($stamp1, 'file', System::callstack());
} }
/** /**
@ -249,7 +196,7 @@ class Logger extends BaseObject
$stamp1 = microtime(true); $stamp1 = microtime(true);
self::$logger->warning($message, $context); self::$logger->warning($message, $context);
self::getApp()->saveTimestamp($stamp1, 'file'); self::getApp()->getProfiler()->saveTimestamp($stamp1, 'file', System::callstack());
} }
/** /**
@ -270,7 +217,7 @@ class Logger extends BaseObject
$stamp1 = microtime(true); $stamp1 = microtime(true);
self::$logger->notice($message, $context); self::$logger->notice($message, $context);
self::getApp()->saveTimestamp($stamp1, 'file'); self::getApp()->getProfiler()->saveTimestamp($stamp1, 'file', System::callstack());
} }
/** /**
@ -293,7 +240,7 @@ class Logger extends BaseObject
$stamp1 = microtime(true); $stamp1 = microtime(true);
self::$logger->info($message, $context); self::$logger->info($message, $context);
self::getApp()->saveTimestamp($stamp1, 'file'); self::getApp()->getProfiler()->saveTimestamp($stamp1, 'file', System::callstack());
} }
/** /**
@ -314,28 +261,28 @@ class Logger extends BaseObject
$stamp1 = microtime(true); $stamp1 = microtime(true);
self::$logger->debug($message, $context); self::$logger->debug($message, $context);
self::getApp()->saveTimestamp($stamp1, 'file'); self::getApp()->getProfiler()->saveTimestamp($stamp1, 'file', System::callstack());
} }
/** /**
* @brief Logs the given message at the given log level * @brief Logs the given message at the given log level
* *
* @param string $msg * @param string $msg
* @param string $level * @param string $level
* *
* @throws \Exception * @throws \Exception
* @deprecated since 2019.03 Use Logger::debug() Logger::info() , ... instead * @deprecated since 2019.03 Use Logger::debug() Logger::info() , ... instead
*/ */
public static function log($msg, $level = LogLevel::INFO) public static function log($msg, $level = LogLevel::INFO)
{ {
if (!isset(self::$logger)) { if (!isset(self::$logger)) {
return; return;
} }
$stamp1 = microtime(true); $stamp1 = microtime(true);
self::$logger->log($level, $msg); self::$logger->log($level, $msg);
self::getApp()->saveTimestamp($stamp1, "file"); self::getApp()->getProfiler()->saveTimestamp($stamp1, "file", System::callstack());
} }
/** /**
* @brief An alternative logger for development. * @brief An alternative logger for development.
@ -347,14 +294,14 @@ class Logger extends BaseObject
* @param string $level * @param string $level
* @throws \Exception * @throws \Exception
*/ */
public static function devLog($msg, $level = LogLevel::DEBUG) public static function devLog($msg, $level = LogLevel::DEBUG)
{ {
if (!isset(self::$logger)) { if (!isset(self::$logger)) {
return; return;
} }
$stamp1 = microtime(true); $stamp1 = microtime(true);
self::$devLogger->log($level, $msg); self::$devLogger->log($level, $msg);
self::getApp()->saveTimestamp($stamp1, "file"); self::getApp()->getProfiler()->saveTimestamp($stamp1, "file", System::callstack());
} }
} }

View file

@ -18,123 +18,76 @@ namespace Friendica\Core;
class PConfig class PConfig
{ {
/** /**
* @var Config\IPConfigAdapter * @var Config\PConfiguration
*/ */
private static $adapter; private static $config;
/**
* @var Config\IPConfigCache
*/
private static $cache;
/** /**
* Initialize the config with only the cache * Initialize the config with only the cache
* *
* @param Config\IPConfigCache $cache The configuration cache * @param Config\PConfiguration $config The configuration cache
*/ */
public static function init(Config\IPConfigCache $cache) public static function init(Config\PConfiguration $config)
{ {
self::$cache = $cache; self::$config = $config;
}
/**
* Add the adapter for DB-backend
*
* @param Config\IPConfigAdapter $adapter
*/
public static function setAdapter(Config\IPConfigAdapter $adapter)
{
self::$adapter = $adapter;
} }
/** /**
* @brief Loads all configuration values of a user's config family into a cached storage. * @brief 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 * @param string $uid The user_id
* the cache ( @see IPConfigCache ) * @param string $cat The category of the configuration value
*
* @param string $uid The user_id
* @param string $family The category of the configuration value
* *
* @return void * @return void
*/ */
public static function load($uid, $family) public static function load($uid, $cat)
{ {
if (!isset(self::$adapter)) { self::$config->load($uid, $cat);
return;
}
self::$adapter->load($uid, $family);
} }
/** /**
* @brief Get a particular user's config variable given the category name * @brief Get a particular user's config variable given the category name
* ($family) and a key. * ($cat) and a key.
*
* Get a particular user's config value from the given category ($family)
* and the $key with the $uid from a cached storage either from the self::$adapter
* (@see IConfigAdapter ) or from the static::$cache (@see IConfigCache ).
* *
* @param string $uid The user_id * @param string $uid The user_id
* @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 string $key The configuration key to query
* @param mixed $default_value optional, The value to return if key is not set (default: null) * @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) * @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 * @return mixed Stored value or null if it does not exist
*/ */
public static function get($uid, $family, $key, $default_value = null, $refresh = false) public static function get($uid, $cat, $key, $default_value = null, $refresh = false)
{ {
if (!isset(self::$adapter)) { return self::$config->get($uid, $cat, $key, $default_value, $refresh);
return self::$cache->getP($uid, $family, $key, $default_value);
}
return self::$adapter->get($uid, $family, $key, $default_value, $refresh);
} }
/** /**
* @brief Sets a configuration value for a user * @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 $uid The user_id
* @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 string $key The configuration key to set
* @param mixed $value The value to store * @param mixed $value The value to store
* *
* @return bool Operation success * @return bool Operation success
*/ */
public static function set($uid, $family, $key, $value) public static function set($uid, $cat, $key, $value)
{ {
if (!isset(self::$adapter)) { return self::$config->set($uid, $cat, $key, $value);
return self::$cache->setP($uid, $family, $key, $value);
}
return self::$adapter->set($uid, $family, $key, $value);
} }
/** /**
* @brief Deletes the given key from the users's configuration. * @brief Deletes the given key from the users's configuration.
* *
* Removes the configured value from the stored cache in self::$config * @param string $uid The user_id
* (@see ConfigCache ) and removes it from the database (@see IConfigAdapter ) * @param string $cat The category of the configuration value
* with the given $uid. * @param string $key The configuration key to delete
* *
* @param string $uid The user_id * @return bool
* @param string $family The category of the configuration value
* @param string $key The configuration key to delete
*
* @return mixed
*/ */
public static function delete($uid, $family, $key) public static function delete($uid, $cat, $key)
{ {
if (!isset(self::$adapter)) { return self::$config->delete($uid, $cat, $key);
return self::$cache->deleteP($uid, $family, $key);
}
return self::$adapter->delete($uid, $family, $key);
} }
} }

View file

@ -74,7 +74,7 @@ class Renderer extends BaseObject
exit(); exit();
} }
$a->saveTimestamp($stamp1, "rendering"); $a->getProfiler()->saveTimestamp($stamp1, "rendering", System::callstack());
return $output; return $output;
} }
@ -101,7 +101,7 @@ class Renderer extends BaseObject
exit(); exit();
} }
$a->saveTimestamp($stamp1, "file"); $a->getProfiler()->saveTimestamp($stamp1, "file", System::callstack());
return $template; return $template;
} }

View file

@ -176,6 +176,12 @@ class System extends BaseObject
exit(); exit();
} }
public static function jsonError($httpCode, $data, $content_type = 'application/json')
{
header($_SERVER["SERVER_PROTOCOL"] . ' ' . $httpCode);
self::jsonExit($data, $content_type);
}
/** /**
* @brief Encodes content to json. * @brief Encodes content to json.
* *
@ -234,21 +240,6 @@ class System extends BaseObject
} }
} }
/**
* Generates a process identifier for the logging
*
* @param string $prefix A given prefix
*
* @return string a generated process identifier
*/
public static function processID($prefix)
{
// We aren't calling any other function here.
// Doing so could easily create an endless loop
$trailer = $prefix . ':' . getmypid() . ':';
return substr($trailer . uniqid('') . mt_rand(), 0, 26);
}
/** /**
* Returns the current Load of the System * Returns the current Load of the System
* *

View file

@ -51,7 +51,7 @@ class Theme
$a = \get_app(); $a = \get_app();
$stamp1 = microtime(true); $stamp1 = microtime(true);
$theme_file = file_get_contents("view/theme/$theme/theme.php"); $theme_file = file_get_contents("view/theme/$theme/theme.php");
$a->saveTimestamp($stamp1, "file"); $a->getProfiler()->saveTimestamp($stamp1, "file", System::callstack());
$result = preg_match("|/\*.*\*/|msU", $theme_file, $matches); $result = preg_match("|/\*.*\*/|msU", $theme_file, $matches);

View file

@ -37,9 +37,13 @@ class Update
} }
if ($build < DB_UPDATE_VERSION) { if ($build < DB_UPDATE_VERSION) {
// When we cannot execute the database update via the worker, we will do it directly if ($via_worker) {
if (!Worker::add(PRIORITY_CRITICAL, 'DBUpdate') && $via_worker) { // Calling the database update directly via the worker enables us to perform database changes to the workerqueue table itself.
// This is a fallback, since normally the database update will be performed by a worker job.
// This worker job doesn't work for changes to the "workerqueue" table itself.
self::run($basePath); self::run($basePath);
} else {
Worker::add(PRIORITY_CRITICAL, 'DBUpdate');
} }
} }
} }
@ -48,19 +52,20 @@ class Update
* Automatic database updates * Automatic database updates
* *
* @param string $basePath The base path of this application * @param string $basePath The base path of this application
* @param bool $force Force the Update-Check even if the lock is set * @param bool $force Force the Update-Check even if the database version doesn't match
* @param bool $override Overrides any running/stuck updates
* @param bool $verbose Run the Update-Check verbose * @param bool $verbose Run the Update-Check verbose
* @param bool $sendMail Sends a Mail to the administrator in case of success/failure * @param bool $sendMail Sends a Mail to the administrator in case of success/failure
* *
* @return string Empty string if the update is successful, error messages otherwise * @return string Empty string if the update is successful, error messages otherwise
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/ */
public static function run($basePath, $force = false, $verbose = false, $sendMail = true) public static function run($basePath, $force = false, $override = false, $verbose = false, $sendMail = true)
{ {
// In force mode, we release the dbupdate lock first // In force mode, we release the dbupdate lock first
// Necessary in case of an stuck update // Necessary in case of an stuck update
if ($force) { if ($override) {
Lock::release('dbupdate'); Lock::release('dbupdate', true);
} }
$build = Config::get('system', 'build'); $build = Config::get('system', 'build');
@ -70,12 +75,12 @@ class Update
Config::set('system', 'build', $build); Config::set('system', 'build', $build);
} }
if ($build != DB_UPDATE_VERSION) { if ($build != DB_UPDATE_VERSION || $force) {
require_once 'update.php'; require_once 'update.php';
$stored = intval($build); $stored = intval($build);
$current = intval(DB_UPDATE_VERSION); $current = intval(DB_UPDATE_VERSION);
if ($stored < $current) { if ($stored < $current || $force) {
Config::load('database'); Config::load('database');
Logger::log('Update from \'' . $stored . '\' to \'' . $current . '\' - starting', Logger::DEBUG); Logger::log('Update from \'' . $stored . '\' to \'' . $current . '\' - starting', Logger::DEBUG);
@ -94,7 +99,7 @@ class Update
// update the structure in one call // update the structure in one call
$retval = DBStructure::update($basePath, $verbose, true); $retval = DBStructure::update($basePath, $verbose, true);
if ($retval) { if (!empty($retval)) {
if ($sendMail) { if ($sendMail) {
self::updateFailed( self::updateFailed(
DB_UPDATE_VERSION, DB_UPDATE_VERSION,
@ -126,8 +131,6 @@ class Update
Lock::release('dbupdate'); Lock::release('dbupdate');
} }
} }
} elseif ($force) {
DBStructure::update($basePath, $verbose, true);
} }
return ''; return '';

View file

@ -8,6 +8,7 @@ use Friendica\BaseObject;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\Model\Process; use Friendica\Model\Process;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
use Friendica\Util\Logger\WorkerLogger;
use Friendica\Util\Network; use Friendica\Util\Network;
/** /**
@ -360,39 +361,19 @@ class Worker
{ {
$a = \get_app(); $a = \get_app();
$mypid = getmypid();
$argc = count($argv); $argc = count($argv);
// Currently deactivated, since the new logger doesn't support this $logger = $a->getLogger();
//$new_process_id = System::processID("wrk"); $workerLogger = new WorkerLogger($logger, $funcname);
$new_process_id = '';
Logger::log("Process ".$mypid." - Prio ".$queue["priority"]." - ID ".$queue["id"].": ".$funcname." ".$queue["parameter"]." - Process PID: ".$new_process_id); $workerLogger ->info("Process start.", ['priority' => $queue["priority"], 'id' => $queue["id"]]);
$stamp = (float)microtime(true); $stamp = (float)microtime(true);
// We use the callstack here to analyze the performance of executed worker entries. // We use the callstack here to analyze the performance of executed worker entries.
// For this reason the variables have to be initialized. // For this reason the variables have to be initialized.
if (Config::get("system", "profiler")) { $a->getProfiler()->reset();
$a->performance["start"] = microtime(true);
$a->performance["database"] = 0;
$a->performance["database_write"] = 0;
$a->performance["cache"] = 0;
$a->performance["cache_write"] = 0;
$a->performance["network"] = 0;
$a->performance["file"] = 0;
$a->performance["rendering"] = 0;
$a->performance["parser"] = 0;
$a->performance["marktime"] = 0;
$a->performance["markstart"] = microtime(true);
$a->callstack = [];
}
// For better logging create a new process id for every worker call
// But preserve the old one for the worker
$old_process_id = $a->process_id;
$a->process_id = $new_process_id;
$a->queue = $queue; $a->queue = $queue;
$up_duration = microtime(true) - self::$up_start; $up_duration = microtime(true) - self::$up_start;
@ -400,13 +381,15 @@ class Worker
// Reset global data to avoid interferences // Reset global data to avoid interferences
unset($_SESSION); unset($_SESSION);
// Set the workerLogger as new default logger
Logger::init($workerLogger);
if ($method_call) { if ($method_call) {
call_user_func_array(sprintf('Friendica\Worker\%s::execute', $funcname), $argv); call_user_func_array(sprintf('Friendica\Worker\%s::execute', $funcname), $argv);
} else { } else {
$funcname($argv, $argc); $funcname($argv, $argc);
} }
Logger::init($logger);
$a->process_id = $old_process_id;
unset($a->queue); unset($a->queue);
$duration = (microtime(true) - $stamp); $duration = (microtime(true) - $stamp);
@ -425,7 +408,7 @@ class Worker
$rest = round(max(0, $up_duration - (self::$db_duration + self::$lock_duration)), 2); $rest = round(max(0, $up_duration - (self::$db_duration + self::$lock_duration)), 2);
$exec = round($duration, 2); $exec = round($duration, 2);
Logger::info('Performance:', ['mode' => self::$mode, 'count' => $dbcount, 'stat' => $dbstat, 'write' => $dbwrite, 'lock' => $dblock, 'total' => $dbtotal, 'rest' => $rest, 'exec' => $exec]); $logger->info('Performance log.', ['mode' => self::$mode, 'count' => $dbcount, 'stat' => $dbstat, 'write' => $dbwrite, 'lock' => $dblock, 'total' => $dbtotal, 'rest' => $rest, 'exec' => $exec]);
self::$up_start = microtime(true); self::$up_start = microtime(true);
self::$db_duration = 0; self::$db_duration = 0;
@ -436,92 +419,23 @@ class Worker
self::$mode = 2; self::$mode = 2;
if ($duration > 3600) { if ($duration > 3600) {
Logger::log("Prio ".$queue["priority"].": ".$queue["parameter"]." - longer than 1 hour (".round($duration/60, 3).")", Logger::DEBUG); $logger->info('Longer than 1 hour.', ['priority' => $queue["priority"], 'id' => $queue["id"], 'duration' => round($duration/60, 3)]);
} elseif ($duration > 600) { } elseif ($duration > 600) {
Logger::log("Prio ".$queue["priority"].": ".$queue["parameter"]." - longer than 10 minutes (".round($duration/60, 3).")", Logger::DEBUG); $logger->info('Longer than 10 minutes.', ['priority' => $queue["priority"], 'id' => $queue["id"], 'duration' => round($duration/60, 3)]);
} elseif ($duration > 300) { } elseif ($duration > 300) {
Logger::log("Prio ".$queue["priority"].": ".$queue["parameter"]." - longer than 5 minutes (".round($duration/60, 3).")", Logger::DEBUG); $logger->info('Longer than 5 minutes.', ['priority' => $queue["priority"], 'id' => $queue["id"], 'duration' => round($duration/60, 3)]);
} elseif ($duration > 120) { } elseif ($duration > 120) {
Logger::log("Prio ".$queue["priority"].": ".$queue["parameter"]." - longer than 2 minutes (".round($duration/60, 3).")", Logger::DEBUG); $logger->info('Longer than 2 minutes.', ['priority' => $queue["priority"], 'id' => $queue["id"], 'duration' => round($duration/60, 3)]);
} }
Logger::log("Process ".$mypid." - Prio ".$queue["priority"]." - ID ".$queue["id"].": ".$funcname." - done in ".number_format($duration, 4)." seconds. Process PID: ".$new_process_id); $workerLogger->info('Process done. ', ['priority' => $queue["priority"], 'id' => $queue["id"], 'duration' => number_format($duration, 4)]);
// Write down the performance values into the log $a->getProfiler()->saveLog($a->getLogger(), "ID " . $queue["id"] . ": " . $funcname);
if (Config::get("system", "profiler")) {
$duration = microtime(true)-$a->performance["start"];
$o = '';
if (Config::get("rendertime", "callstack")) {
if (isset($a->callstack["database"])) {
$o .= "\nDatabase Read:\n";
foreach ($a->callstack["database"] as $func => $time) {
$time = round($time, 3);
if ($time > 0) {
$o .= $func.": ".$time."\n";
}
}
}
if (isset($a->callstack["database_write"])) {
$o .= "\nDatabase Write:\n";
foreach ($a->callstack["database_write"] as $func => $time) {
$time = round($time, 3);
if ($time > 0) {
$o .= $func.": ".$time."\n";
}
}
}
if (isset($a->callstack["dache"])) {
$o .= "\nCache Read:\n";
foreach ($a->callstack["dache"] as $func => $time) {
$time = round($time, 3);
if ($time > 0) {
$o .= $func.": ".$time."\n";
}
}
}
if (isset($a->callstack["dache_write"])) {
$o .= "\nCache Write:\n";
foreach ($a->callstack["dache_write"] as $func => $time) {
$time = round($time, 3);
if ($time > 0) {
$o .= $func.": ".$time."\n";
}
}
}
if (isset($a->callstack["network"])) {
$o .= "\nNetwork:\n";
foreach ($a->callstack["network"] as $func => $time) {
$time = round($time, 3);
if ($time > 0) {
$o .= $func.": ".$time."\n";
}
}
}
}
Logger::log(
"ID ".$queue["id"].": ".$funcname.": ".sprintf(
"DB: %s/%s, Cache: %s/%s, Net: %s, I/O: %s, Other: %s, Total: %s".$o,
number_format($a->performance["database"] - $a->performance["database_write"], 2),
number_format($a->performance["database_write"], 2),
number_format($a->performance["cache"], 2),
number_format($a->performance["cache_write"], 2),
number_format($a->performance["network"], 2),
number_format($a->performance["file"], 2),
number_format($duration - ($a->performance["database"]
+ $a->performance["cache"] + $a->performance["cache_write"]
+ $a->performance["network"] + $a->performance["file"]), 2),
number_format($duration, 2)
),
Logger::DEBUG
);
}
$cooldown = Config::get("system", "worker_cooldown", 0); $cooldown = Config::get("system", "worker_cooldown", 0);
if ($cooldown > 0) { if ($cooldown > 0) {
Logger::log("Process ".$mypid." - Prio ".$queue["priority"]." - ID ".$queue["id"].": ".$funcname." - in cooldown for ".$cooldown." seconds"); $logger->info('Cooldown.', ['priority' => $queue["priority"], 'id' => $queue["id"], 'cooldown' => $cooldown]);
sleep($cooldown); sleep($cooldown);
} }
} }
@ -943,7 +857,7 @@ class Worker
} }
if (!empty($waiting)) { if (!empty($waiting)) {
$priority = array_shift(array_keys($waiting)); $priority = array_keys($waiting)[0];
Logger::info('No underassigned priority found, now taking the highest priority.', ['priority' => $priority]); Logger::info('No underassigned priority found, now taking the highest priority.', ['priority' => $priority]);
return $priority; return $priority;
} }

View file

@ -2,10 +2,11 @@
namespace Friendica\Database; namespace Friendica\Database;
use Friendica\Core\Config\IConfigCache; use Friendica\Core\Config\Cache\IConfigCache;
use Friendica\Core\Logger; use Friendica\Core\Logger;
use Friendica\Core\System; use Friendica\Core\System;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
use Friendica\Util\Profiler;
use mysqli; use mysqli;
use mysqli_result; use mysqli_result;
use mysqli_stmt; use mysqli_stmt;
@ -35,6 +36,14 @@ class DBA
* @var IConfigCache * @var IConfigCache
*/ */
private static $configCache; private static $configCache;
/**
* @var Profiler
*/
private static $profiler;
/**
* @var string
*/
private static $basePath;
private static $server_info = ''; private static $server_info = '';
private static $connection; private static $connection;
private static $driver; private static $driver;
@ -50,14 +59,16 @@ class DBA
private static $db_name = ''; private static $db_name = '';
private static $db_charset = ''; private static $db_charset = '';
public static function connect($configCache, $serveraddr, $user, $pass, $db, $charset = null) public static function connect($basePath, IConfigCache $configCache, Profiler $profiler, $serveraddr, $user, $pass, $db, $charset = null)
{ {
if (!is_null(self::$connection) && self::connected()) { if (!is_null(self::$connection) && self::connected()) {
return true; return true;
} }
// We are storing these values for being able to perform a reconnect // We are storing these values for being able to perform a reconnect
self::$basePath = $basePath;
self::$configCache = $configCache; self::$configCache = $configCache;
self::$profiler = $profiler;
self::$db_serveraddr = $serveraddr; self::$db_serveraddr = $serveraddr;
self::$db_user = $user; self::$db_user = $user;
self::$db_pass = $pass; self::$db_pass = $pass;
@ -158,7 +169,7 @@ class DBA
public static function reconnect() { public static function reconnect() {
self::disconnect(); self::disconnect();
$ret = self::connect(self::$configCache, self::$db_serveraddr, self::$db_user, self::$db_pass, self::$db_name, self::$db_charset); $ret = self::connect(self::$basePath, self::$configCache, self::$profiler, self::$db_serveraddr, self::$db_user, self::$db_pass, self::$db_name, self::$db_charset);
return $ret; return $ret;
} }
@ -392,7 +403,6 @@ class DBA
* @throws \Exception * @throws \Exception
*/ */
public static function p($sql) { public static function p($sql) {
$a = \get_app();
$stamp1 = microtime(true); $stamp1 = microtime(true);
@ -415,7 +425,7 @@ class DBA
if ((substr_count($sql, '?') != count($args)) && (count($args) > 0)) { if ((substr_count($sql, '?') != count($args)) && (count($args) > 0)) {
// Question: Should we continue or stop the query here? // Question: Should we continue or stop the query here?
Logger::log('Parameter mismatch. Query "'.$sql.'" - Parameters '.print_r($args, true), Logger::DEBUG); Logger::warning('Query parameters mismatch.', ['query' => $sql, 'args' => $args, 'callstack' => System::callstack()]);
} }
$sql = self::cleanQuery($sql); $sql = self::cleanQuery($sql);
@ -582,7 +592,7 @@ class DBA
self::$errorno = $errorno; self::$errorno = $errorno;
} }
$a->saveTimestamp($stamp1, 'database'); self::$profiler->saveTimestamp($stamp1, 'database', System::callstack());
if (self::$configCache->get('system', 'db_log')) { if (self::$configCache->get('system', 'db_log')) {
$stamp2 = microtime(true); $stamp2 = microtime(true);
@ -611,7 +621,6 @@ class DBA
* @throws \Exception * @throws \Exception
*/ */
public static function e($sql) { public static function e($sql) {
$a = \get_app();
$stamp = microtime(true); $stamp = microtime(true);
@ -654,7 +663,7 @@ class DBA
self::$errorno = $errorno; self::$errorno = $errorno;
} }
$a->saveTimestamp($stamp, "database_write"); self::$profiler->saveTimestamp($stamp, "database_write", System::callstack());
return $retval; return $retval;
} }
@ -777,7 +786,6 @@ class DBA
* @return array current row * @return array current row
*/ */
public static function fetch($stmt) { public static function fetch($stmt) {
$a = \get_app();
$stamp1 = microtime(true); $stamp1 = microtime(true);
@ -824,7 +832,7 @@ class DBA
} }
} }
$a->saveTimestamp($stamp1, 'database'); self::$profiler->saveTimestamp($stamp1, 'database', System::callstack());
return $columns; return $columns;
} }
@ -1031,7 +1039,7 @@ class DBA
* This process must only be started once, since the value is cached. * This process must only be started once, since the value is cached.
*/ */
private static function buildRelationData() { private static function buildRelationData() {
$definition = DBStructure::definition(self::$configCache->get('system', 'basepath')); $definition = DBStructure::definition(self::$basePath);
foreach ($definition AS $table => $structure) { foreach ($definition AS $table => $structure) {
foreach ($structure['fields'] AS $field => $field_struct) { foreach ($structure['fields'] AS $field => $field_struct) {
@ -1534,7 +1542,6 @@ class DBA
* @return boolean was the close successful? * @return boolean was the close successful?
*/ */
public static function close($stmt) { public static function close($stmt) {
$a = \get_app();
$stamp1 = microtime(true); $stamp1 = microtime(true);
@ -1562,7 +1569,7 @@ class DBA
break; break;
} }
$a->saveTimestamp($stamp1, 'database'); self::$profiler->saveTimestamp($stamp1, 'database', System::callstack());
return $ret; return $ret;
} }

View file

@ -2,51 +2,66 @@
namespace Friendica\Factory; namespace Friendica\Factory;
use Friendica\Core;
use Friendica\Core\Config; use Friendica\Core\Config;
use Friendica\Core\Config\Adapter;
use Friendica\Core\Config\Cache;
class ConfigFactory class ConfigFactory
{ {
/** /**
* @param Config\ConfigCacheLoader $loader The Config Cache loader (INI/config/.htconfig) * @param Cache\ConfigCacheLoader $loader The Config Cache loader (INI/config/.htconfig)
* *
* @return Config\ConfigCache * @return Cache\ConfigCache
*/ */
public static function createCache(Config\ConfigCacheLoader $loader) public static function createCache(Cache\ConfigCacheLoader $loader)
{ {
$configCache = new Config\ConfigCache(); $configCache = new Cache\ConfigCache();
$loader->loadConfigFiles($configCache); $loader->loadConfigFiles($configCache);
return $configCache; return $configCache;
} }
/** /**
* @param string $type The adapter type * @param Cache\ConfigCache $configCache The config cache of this adapter
* @param Config\IConfigCache $config The config cache of this adapter
* *
* @return Config\IConfigAdapter * @return Config\Configuration
*/ */
public static function createConfig($type, Config\IConfigCache $config) public static function createConfig(Cache\ConfigCache $configCache)
{ {
if ($type == 'preload') { if ($configCache->get('system', 'config_adapter') === 'preload') {
return new Config\PreloadConfigAdapter($config); $configAdapter = new Adapter\PreloadConfigAdapter();
} else { } else {
return new Config\JITConfigAdapter($config); $configAdapter = new Adapter\JITConfigAdapter();
} }
$configuration = new Config\Configuration($configCache, $configAdapter);
// Set the config in the static container for legacy usage
Core\Config::init($configuration);
return $configuration;
} }
/** /**
* @param string $type The adapter type * @param Cache\ConfigCache $configCache The config cache of this adapter
* @param Config\IPConfigCache $config The config cache of this adapter * @param int $uid The UID of the current user
* @param int $uid The UID of the current user
* *
* @return Config\IPConfigAdapter * @return Config\PConfiguration
*/ */
public static function createPConfig($type, Config\IPConfigCache $config, $uid = null) public static function createPConfig(Cache\ConfigCache $configCache, $uid = null)
{ {
if ($type == 'preload') { if ($configCache->get('system', 'config_adapter') === 'preload') {
return new Config\PreloadPConfigAdapter($config, $uid); $configAdapter = new Adapter\PreloadPConfigAdapter($uid);
} else { } else {
return new Config\JITPConfigAdapter($config); $configAdapter = new Adapter\JITPConfigAdapter();
} }
$configuration = new Config\PConfiguration($configCache, $configAdapter);
// Set the config in the static container for legacy usage
Core\PConfig::init($configuration);
return $configuration;
} }
} }

61
src/Factory/DBFactory.php Normal file
View file

@ -0,0 +1,61 @@
<?php
namespace Friendica\Factory;
use Friendica\Core\Config\Cache;
use Friendica\Database;
use Friendica\Util\Profiler;
class DBFactory
{
/**
* Initialize the DBA connection
*
* @param string $basePath The basepath of the application
* @param Cache\IConfigCache $configCache The configuration cache
* @param Profiler $profiler The profiler
* @param array $server The $_SERVER variables
*
* @throws \Exception if connection went bad
*
* @todo refactor basedir during https://github.com/friendica/friendica/issues/6720
*/
public static function init($basePath, Cache\IConfigCache $configCache, Profiler $profiler, array $server)
{
if (Database\DBA::connected()) {
return;
}
$db_host = $configCache->get('database', 'hostname');
$db_user = $configCache->get('database', 'username');
$db_pass = $configCache->get('database', 'password');
$db_data = $configCache->get('database', 'database');
$charset = $configCache->get('database', 'charset');
// Use environment variables for mysql if they are set beforehand
if (!empty($server['MYSQL_HOST'])
&& !empty($server['MYSQL_USERNAME'] || !empty($server['MYSQL_USER']))
&& $server['MYSQL_PASSWORD'] !== false
&& !empty($server['MYSQL_DATABASE']))
{
$db_host = $server['MYSQL_HOST'];
if (!empty($server['MYSQL_PORT'])) {
$db_host .= ':' . $server['MYSQL_PORT'];
}
if (!empty($server['MYSQL_USERNAME'])) {
$db_user = $server['MYSQL_USERNAME'];
} else {
$db_user = $server['MYSQL_USER'];
}
$db_pass = (string) $server['MYSQL_PASSWORD'];
$db_data = $server['MYSQL_DATABASE'];
}
if (Database\DBA::connect($basePath, $configCache, $profiler, $db_host, $db_user, $db_pass, $db_data, $charset)) {
// Loads DB_UPDATE_VERSION constant
Database\DBStructure::definition($basePath, false);
}
unset($db_host, $db_user, $db_pass, $db_data, $charset);
}
}

View file

@ -0,0 +1,37 @@
<?php
namespace Friendica\Factory;
use Friendica\App;
use Friendica\Core\Config\Cache;
use Friendica\Factory;
use Friendica\Util\BasePath;
class DependencyFactory
{
/**
* Setting all default-dependencies of a friendica execution
*
* @param string $channel The channel of this execution
* @param string $directory The base directory
* @param bool $isBackend True, if it's a backend execution, otherwise false (Default true)
*
* @return App The application
*
* @throws \Exception
*/
public static function setUp($channel, $directory, $isBackend = true)
{
$basePath = BasePath::create($directory, $_SERVER);
$configLoader = new Cache\ConfigCacheLoader($basePath);
$configCache = Factory\ConfigFactory::createCache($configLoader);
$profiler = Factory\ProfilerFactory::create($configCache);
Factory\DBFactory::init($basePath, $configCache, $profiler, $_SERVER);
$config = Factory\ConfigFactory::createConfig($configCache);
// needed to call PConfig::init()
Factory\ConfigFactory::createPConfig($configCache);
$logger = Factory\LoggerFactory::create($channel, $config);
return new App($basePath, $config, $logger, $profiler, $isBackend);
}
}

View file

@ -2,10 +2,13 @@
namespace Friendica\Factory; namespace Friendica\Factory;
use Friendica\Core\Config\ConfigCache; use Friendica\Core\Config\Configuration;
use Friendica\Core\Logger;
use Friendica\Network\HTTPException\InternalServerErrorException; use Friendica\Network\HTTPException\InternalServerErrorException;
use Friendica\Util\Logger\FriendicaDevelopHandler; use Friendica\Util\Logger\FriendicaDevelopHandler;
use Friendica\Util\Logger\FriendicaIntrospectionProcessor; use Friendica\Util\Logger\FriendicaIntrospectionProcessor;
use Friendica\Util\Logger\WorkerLogger;
use Friendica\Util\Profiler;
use Monolog; use Monolog;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel; use Psr\Log\LogLevel;
@ -17,32 +20,45 @@ use Psr\Log\LogLevel;
*/ */
class LoggerFactory class LoggerFactory
{ {
/**
* A list of classes, which shouldn't get logged
* @var array
*/
private static $ignoreClassList = [
Logger::class,
Profiler::class,
WorkerLogger::class
];
/** /**
* Creates a new PSR-3 compliant logger instances * Creates a new PSR-3 compliant logger instances
* *
* @param string $channel The channel of the logger instance * @param string $channel The channel of the logger instance
* @param ConfigCache $config The config * @param Configuration $config The config
* *
* @return LoggerInterface The PSR-3 compliant logger instance * @return LoggerInterface The PSR-3 compliant logger instance
*/ */
public static function create($channel, ConfigCache $config = null) public static function create($channel, Configuration $config)
{ {
$logger = new Monolog\Logger($channel); $logger = new Monolog\Logger($channel);
$logger->pushProcessor(new Monolog\Processor\PsrLogMessageProcessor()); $logger->pushProcessor(new Monolog\Processor\PsrLogMessageProcessor());
$logger->pushProcessor(new Monolog\Processor\ProcessIdProcessor()); $logger->pushProcessor(new Monolog\Processor\ProcessIdProcessor());
$logger->pushProcessor(new Monolog\Processor\UidProcessor()); $logger->pushProcessor(new Monolog\Processor\UidProcessor());
$logger->pushProcessor(new FriendicaIntrospectionProcessor(LogLevel::DEBUG, ['Friendica\\Core\\Logger'])); $logger->pushProcessor(new FriendicaIntrospectionProcessor(LogLevel::DEBUG, self::$ignoreClassList));
if (isset($config)) { $debugging = $config->get('system', 'debugging');
$debugging = $config->get('system', 'debugging'); $stream = $config->get('system', 'logfile');
$stream = $config->get('system', 'logfile'); $level = $config->get('system', 'loglevel');
$level = $config->get('system', 'loglevel');
if ($debugging) { if ($debugging) {
static::addStreamHandler($logger, $stream, $level); $loglevel = self::mapLegacyConfigDebugLevel((string)$level);
} static::addStreamHandler($logger, $stream, $loglevel);
} else {
static::addVoidHandler($logger);
} }
Logger::init($logger);
return $logger; return $logger;
} }
@ -54,25 +70,71 @@ class LoggerFactory
* *
* It should never get filled during normal usage of Friendica * It should never get filled during normal usage of Friendica
* *
* @param string $channel The channel of the logger instance * @param string $channel The channel of the logger instance
* @param string $developerIp The IP of the developer who wants to use the logger * @param Configuration $config The config
* *
* @return LoggerInterface The PSR-3 compliant logger instance * @return LoggerInterface The PSR-3 compliant logger instance
*/ */
public static function createDev($channel, $developerIp) public static function createDev($channel, Configuration $config)
{ {
$debugging = $config->get('system', 'debugging');
$stream = $config->get('system', 'dlogfile');
$developerIp = $config->get('system', 'dlogip');
if (!isset($developerIp) || !$debugging) {
return null;
}
$logger = new Monolog\Logger($channel); $logger = new Monolog\Logger($channel);
$logger->pushProcessor(new Monolog\Processor\PsrLogMessageProcessor()); $logger->pushProcessor(new Monolog\Processor\PsrLogMessageProcessor());
$logger->pushProcessor(new Monolog\Processor\ProcessIdProcessor()); $logger->pushProcessor(new Monolog\Processor\ProcessIdProcessor());
$logger->pushProcessor(new Monolog\Processor\UidProcessor()); $logger->pushProcessor(new Monolog\Processor\UidProcessor());
$logger->pushProcessor(new FriendicaIntrospectionProcessor(LogLevel::DEBUG, ['Friendica\\Core\\Logger'])); $logger->pushProcessor(new FriendicaIntrospectionProcessor(LogLevel::DEBUG, self::$ignoreClassList));
$logger->pushHandler(new FriendicaDevelopHandler($developerIp)); $logger->pushHandler(new FriendicaDevelopHandler($developerIp));
static::addStreamHandler($logger, $stream, LogLevel::DEBUG);
Logger::setDevLogger($logger);
return $logger; return $logger;
} }
/**
* Mapping a legacy level to the PSR-3 compliant levels
* @see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md#5-psrlogloglevel
*
* @param string $level the level to be mapped
*
* @return string the PSR-3 compliant level
*/
private static function mapLegacyConfigDebugLevel($level)
{
switch ($level) {
// legacy WARNING
case "0":
return LogLevel::ERROR;
// legacy INFO
case "1":
return LogLevel::WARNING;
// legacy TRACE
case "2":
return LogLevel::NOTICE;
// legacy DEBUG
case "3":
return LogLevel::INFO;
// legacy DATA
case "4":
return LogLevel::DEBUG;
// legacy ALL
case "5":
return LogLevel::DEBUG;
// default if nothing set
default:
return $level;
}
}
/** /**
* Adding a handler to a given logger instance * Adding a handler to a given logger instance
* *
@ -94,6 +156,7 @@ class LoggerFactory
if (!is_int($loglevel)) { if (!is_int($loglevel)) {
$loglevel = LogLevel::NOTICE; $loglevel = LogLevel::NOTICE;
} }
$fileHandler = new Monolog\Handler\StreamHandler($stream, $loglevel); $fileHandler = new Monolog\Handler\StreamHandler($stream, $loglevel);
$formatter = new Monolog\Formatter\LineFormatter("%datetime% %channel% [%level_name%]: %message% %context% %extra%\n"); $formatter = new Monolog\Formatter\LineFormatter("%datetime% %channel% [%level_name%]: %message% %context% %extra%\n");
@ -105,31 +168,10 @@ class LoggerFactory
} }
} }
/** public static function addVoidHandler($logger)
* This method enables the test mode of a given logger
*
* @param LoggerInterface $logger The logger
*
* @return Monolog\Handler\TestHandler the Handling for tests
*
* @throws InternalServerErrorException if the logger is incompatible to the logger factory
*/
public static function enableTest($logger)
{ {
if ($logger instanceof Monolog\Logger) { if ($logger instanceof Monolog\Logger) {
// disable every handler so far
$logger->pushHandler(new Monolog\Handler\NullHandler()); $logger->pushHandler(new Monolog\Handler\NullHandler());
// enable the test handler
$fileHandler = new Monolog\Handler\TestHandler();
$formatter = new Monolog\Formatter\LineFormatter("%datetime% %channel% [%level_name%]: %message% %context% %extra%\n");
$fileHandler->setFormatter($formatter);
$logger->pushHandler($fileHandler);
return $fileHandler;
} else {
throw new InternalServerErrorException('Logger instance incompatible for MonologFactory');
} }
} }
} }

View file

@ -0,0 +1,26 @@
<?php
namespace Friendica\Factory;
use Friendica\Core\Config\Cache\IConfigCache;
use Friendica\Util\Profiler;
class ProfilerFactory
{
/**
* Creates a Profiler for the current execution
*
* @param IConfigCache $configCache The configuration cache
*
* @return Profiler
*/
public static function create(IConfigCache $configCache)
{
$enabled = $configCache->get('system', 'profiler');
$enabled = isset($enabled) && $enabled !== '0';
$renderTime = $configCache->get('rendertime', 'callstack');
$renderTime = isset($renderTime) && $renderTime !== '0';
return new Profiler($enabled, $renderTime);
}
}

View file

@ -232,6 +232,11 @@ class Contact extends BaseObject
} }
DBA::update('user-contact', ['blocked' => $blocked], ['cid' => $cdata['public'], 'uid' => $uid], true); DBA::update('user-contact', ['blocked' => $blocked], ['cid' => $cdata['public'], 'uid' => $uid], true);
if ($blocked) {
// Blocked contact can't be in any group
self::removeFromGroups($cid);
}
} }
/** /**
@ -1001,7 +1006,7 @@ class Contact extends BaseObject
$sparkle = false; $sparkle = false;
if (($contact['network'] === Protocol::DFRN) && !$contact['self']) { if (($contact['network'] === Protocol::DFRN) && !$contact['self']) {
$sparkle = true; $sparkle = true;
$profile_link = System::baseUrl() . '/redir/' . $contact['id']; $profile_link = System::baseUrl() . '/redir/' . $contact['id'] . '?url=' . $contact['url'];
} else { } else {
$profile_link = $contact['url']; $profile_link = $contact['url'];
} }
@ -1011,9 +1016,9 @@ class Contact extends BaseObject
} }
if ($sparkle) { if ($sparkle) {
$status_link = $profile_link . '?url=status'; $status_link = $profile_link . '?tab=status';
$photos_link = $profile_link . '?url=photos'; $photos_link = str_replace('/profile/', '/photos/', $profile_link);
$profile_link = $profile_link . '?url=profile'; $profile_link = $profile_link . '?tab=profile';
} }
if (in_array($contact['network'], [Protocol::DFRN, Protocol::DIASPORA]) && !$contact['self']) { if (in_array($contact['network'], [Protocol::DFRN, Protocol::DIASPORA]) && !$contact['self']) {
@ -2184,7 +2189,7 @@ class Contact extends BaseObject
{ {
$contact = DBA::selectFirst('contact', ['id', 'network', 'url', 'uid'], ['id' => $cid]); $contact = DBA::selectFirst('contact', ['id', 'network', 'url', 'uid'], ['id' => $cid]);
return self::magicLinkbyContact($contact, $url); return self::magicLinkByContact($contact, $url);
} }
/** /**
@ -2197,7 +2202,7 @@ class Contact extends BaseObject
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException * @throws \ImagickException
*/ */
public static function magicLinkbyContact($contact, $url = '') public static function magicLinkByContact($contact, $url = '')
{ {
if ((!local_user() && !remote_user()) || ($contact['network'] != Protocol::DFRN)) { if ((!local_user() && !remote_user()) || ($contact['network'] != Protocol::DFRN)) {
return $url ?: $contact['url']; // Equivalent to ($url != '') ? $url : $contact['url']; return $url ?: $contact['url']; // Equivalent to ($url != '') ? $url : $contact['url'];
@ -2220,4 +2225,9 @@ class Contact extends BaseObject
return $redirect; return $redirect;
} }
public static function removeFromGroups($contact_id)
{
return DBA::delete('group_member', ['contact-id' => $contact_id]);
}
} }

View file

@ -16,6 +16,26 @@ use Friendica\Database\DBA;
*/ */
class Group extends BaseObject class Group extends BaseObject
{ {
/**
*
*
* @param int $group_id
* @return bool
* @throws \Exception
*/
public static function exists($group_id, $uid = null)
{
$condition = ['id' => $group_id, 'deleted' => false];
if (isset($uid)) {
$condition = [
'uid' => $uid
];
}
return DBA::exists('group', $condition);
}
/** /**
* @brief Create a new contact group * @brief Create a new contact group
* *

View file

@ -1336,7 +1336,11 @@ class Item extends BaseObject
$expire_date = time() - ($expire_interval * 86400); $expire_date = time() - ($expire_interval * 86400);
$created_date = strtotime($item['created']); $created_date = strtotime($item['created']);
if ($created_date < $expire_date) { if ($created_date < $expire_date) {
Logger::log('item-store: item created ('.date('c', $created_date).') before expiration time ('.date('c', $expire_date).'). ignored. ' . print_r($item,true), Logger::DEBUG); Logger::notice('Item created before expiration interval.', [
'created' => date('c', $created_date),
'expired' => date('c', $expire_date),
'$item' => $item
]);
return 0; return 0;
} }
} }
@ -1354,7 +1358,13 @@ class Item extends BaseObject
if (DBA::isResult($existing)) { if (DBA::isResult($existing)) {
// We only log the entries with a different user id than 0. Otherwise we would have too many false positives // We only log the entries with a different user id than 0. Otherwise we would have too many false positives
if ($uid != 0) { if ($uid != 0) {
Logger::log("Item with uri ".$item['uri']." already existed for user ".$uid." with id ".$existing["id"]." target network ".$existing["network"]." - new network: ".$item['network']); Logger::notice('Item already existed for user', [
'uri' => $item['uri'],
'uid' => $uid,
'network' => $item['network'],
'existing_id' => $existing["id"],
'existing_network' => $existing["network"]
]);
} }
return $existing["id"]; return $existing["id"];
@ -1405,7 +1415,7 @@ class Item extends BaseObject
// When there is no content then we don't post it // When there is no content then we don't post it
if ($item['body'].$item['title'] == '') { if ($item['body'].$item['title'] == '') {
Logger::log('No body, no title.'); Logger::notice('No body, no title.');
return 0; return 0;
} }
@ -1432,7 +1442,7 @@ class Item extends BaseObject
$item['author-id'] = defaults($item, 'author-id', Contact::getIdForURL($item["author-link"], 0, false, $default)); $item['author-id'] = defaults($item, 'author-id', Contact::getIdForURL($item["author-link"], 0, false, $default));
if (Contact::isBlocked($item["author-id"])) { if (Contact::isBlocked($item["author-id"])) {
Logger::log('Contact '.$item["author-id"].' is blocked, item '.$item["uri"].' will not be stored'); Logger::notice('Author is blocked node-wide', ['author-link' => $item["author-link"], 'item-uri' => $item["uri"]]);
return 0; return 0;
} }
@ -1442,22 +1452,27 @@ class Item extends BaseObject
$item['owner-id'] = defaults($item, 'owner-id', Contact::getIdForURL($item["owner-link"], 0, false, $default)); $item['owner-id'] = defaults($item, 'owner-id', Contact::getIdForURL($item["owner-link"], 0, false, $default));
if (Contact::isBlocked($item["owner-id"])) { if (Contact::isBlocked($item["owner-id"])) {
Logger::log('Contact '.$item["owner-id"].' is blocked, item '.$item["uri"].' will not be stored'); Logger::notice('Owner is blocked node-wide', ['owner-link' => $item["owner-link"], 'item-uri' => $item["uri"]]);
return 0; return 0;
} }
if ($item['network'] == Protocol::PHANTOM) { if ($item['network'] == Protocol::PHANTOM) {
Logger::log('Missing network. Called by: '.System::callstack(), Logger::DEBUG);
$item['network'] = Protocol::DFRN; $item['network'] = Protocol::DFRN;
Logger::log("Set network to " . $item["network"] . " for " . $item["uri"], Logger::DEBUG); Logger::notice('Missing network, setting to {network}.', [
'uri' => $item["uri"],
'network' => $item['network'],
'callstack' => System::callstack()
]);
} }
// Checking if there is already an item with the same guid // Checking if there is already an item with the same guid
Logger::log('Checking for an item for user '.$item['uid'].' on network '.$item['network'].' with the guid '.$item['guid'], Logger::DEBUG);
$condition = ['guid' => $item['guid'], 'network' => $item['network'], 'uid' => $item['uid']]; $condition = ['guid' => $item['guid'], 'network' => $item['network'], 'uid' => $item['uid']];
if (self::exists($condition)) { if (self::exists($condition)) {
Logger::log('found item with guid '.$item['guid'].' for user '.$item['uid'].' on network '.$item['network'], Logger::DEBUG); Logger::notice('Found already existing item', [
'guid' => $item['guid'],
'uid' => $item['uid'],
'network' => $item['network']
]);
return 0; return 0;
} }

View file

@ -7,6 +7,7 @@
namespace Friendica\Model; namespace Friendica\Model;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use \BadMethodCallException;
class ItemDeliveryData class ItemDeliveryData
{ {
@ -71,7 +72,7 @@ class ItemDeliveryData
public static function insert($item_id, array $fields) public static function insert($item_id, array $fields)
{ {
if (empty($item_id)) { if (empty($item_id)) {
throw new \BadMethodCallException('Empty item_id'); throw new BadMethodCallException('Empty item_id');
} }
$fields['iid'] = $item_id; $fields['iid'] = $item_id;
@ -92,7 +93,7 @@ class ItemDeliveryData
public static function update($item_id, array $fields) public static function update($item_id, array $fields)
{ {
if (empty($item_id)) { if (empty($item_id)) {
throw new \BadMethodCallException('Empty item_id'); throw new BadMethodCallException('Empty item_id');
} }
if (empty($fields)) { if (empty($fields)) {
@ -113,7 +114,7 @@ class ItemDeliveryData
public static function delete($item_id) public static function delete($item_id)
{ {
if (empty($item_id)) { if (empty($item_id)) {
throw new \BadMethodCallException('Empty item_id'); throw new BadMethodCallException('Empty item_id');
} }
return DBA::delete('item-delivery-data', ['iid' => $item_id]); return DBA::delete('item-delivery-data', ['iid' => $item_id]);

View file

@ -10,8 +10,8 @@ use Friendica\BaseObject;
use Friendica\Core\Cache; use Friendica\Core\Cache;
use Friendica\Core\Config; use Friendica\Core\Config;
use Friendica\Core\L10n; use Friendica\Core\L10n;
use Friendica\Core\System;
use Friendica\Core\StorageManager; use Friendica\Core\StorageManager;
use Friendica\Core\System;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\Database\DBStructure; use Friendica\Database\DBStructure;
use Friendica\Model\Storage\IStorage; use Friendica\Model\Storage\IStorage;
@ -173,6 +173,8 @@ class Photo extends BaseObject
*/ */
public static function getImageForPhoto(array $photo) public static function getImageForPhoto(array $photo)
{ {
$data = "";
if ($photo["backend-class"] == "") { if ($photo["backend-class"] == "") {
// legacy data storage in "data" column // legacy data storage in "data" column
$i = self::selectFirst(["data"], ["id" => $photo["id"]]); $i = self::selectFirst(["data"], ["id" => $photo["id"]]);
@ -189,6 +191,7 @@ class Photo extends BaseObject
if ($data === "") { if ($data === "") {
return null; return null;
} }
return new Image($data, $photo["type"]); return new Image($data, $photo["type"]);
} }
@ -200,7 +203,7 @@ class Photo extends BaseObject
*/ */
private static function getFields() private static function getFields()
{ {
$allfields = DBStructure::definition(false); $allfields = DBStructure::definition(self::getApp()->getBasePath(), false);
$fields = array_keys($allfields["photo"]["fields"]); $fields = array_keys($allfields["photo"]["fields"]);
array_splice($fields, array_search("data", $fields), 1); array_splice($fields, array_search("data", $fields), 1);
return $fields; return $fields;
@ -219,11 +222,13 @@ class Photo extends BaseObject
{ {
$fields = self::getFields(); $fields = self::getFields();
$values = array_fill(0, count($fields), ""); $values = array_fill(0, count($fields), "");
$photo = array_combine($fields, $values); $photo = array_combine($fields, $values);
$photo["backend-class"] = Storage\SystemResource::class; $photo["backend-class"] = Storage\SystemResource::class;
$photo["backend-ref"] = $filename; $photo["backend-ref"] = $filename;
$photo["type"] = $mimetype; $photo["type"] = $mimetype;
$photo["cacheable"] = false; $photo["cacheable"] = false;
return $photo; return $photo;
} }

View file

@ -787,7 +787,7 @@ class Profile
$profile['marital']['with'] = $a->profile['with']; $profile['marital']['with'] = $a->profile['with'];
} }
if (strlen($a->profile['howlong']) && $a->profile['howlong'] >= DBA::NULL_DATETIME) { if (strlen($a->profile['howlong']) && $a->profile['howlong'] > DBA::NULL_DATETIME) {
$profile['howlong'] = Temporal::getRelativeDate($a->profile['howlong'], L10n::t('for %1$d %2$s')); $profile['howlong'] = Temporal::getRelativeDate($a->profile['howlong'], L10n::t('for %1$d %2$s'));
} }

View file

@ -51,7 +51,13 @@ class Database implements IStorage
return DBA::delete('storage', ['id' => $ref]); return DBA::delete('storage', ['id' => $ref]);
} }
public static function getOptions() { return []; } public static function getOptions()
{
return [];
}
public static function saveOptions($data) { return []; } public static function saveOptions($data)
{
return [];
}
} }

View file

@ -6,6 +6,8 @@
namespace Friendica\Model\Storage; namespace Friendica\Model\Storage;
use \BadMethodCallException;
/** /**
* @brief System resource storage class * @brief System resource storage class
* *
@ -32,12 +34,12 @@ class SystemResource implements IStorage
public static function put($data, $filename = "") public static function put($data, $filename = "")
{ {
throw new \BadMethodCallException(); throw new BadMethodCallException();
} }
public static function delete($filename) public static function delete($filename)
{ {
throw new \BadMethodCallException(); throw new BadMethodCallException();
} }
public static function getOptions() public static function getOptions()

View file

@ -1,37 +1,82 @@
<?php <?php
/** /**
* @file src/Model/Term * @file src/Model/Term.php
*/ */
namespace Friendica\Model; namespace Friendica\Model;
use Friendica\Core\Logger;
use Friendica\Core\System; use Friendica\Core\System;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\Util\Strings;
/**
* Class Term
*
* This Model class handles term table interactions.
* This tables stores relevant terms related to posts, photos and searches, like hashtags, mentions and
* user-applied categories.
*
* @package Friendica\Model
*/
class Term class Term
{ {
public static function tagTextFromItemId($itemid) const UNKNOWN = 0;
{ const HASHTAG = 1;
$tag_text = ''; const MENTION = 2;
$condition = ['otype' => TERM_OBJ_POST, 'oid' => $itemid, 'type' => [TERM_HASHTAG, TERM_MENTION]]; const CATEGORY = 3;
$tags = DBA::select('term', [], $condition); const PCATEGORY = 4;
while ($tag = DBA::fetch($tags)) { const FILE = 5;
if ($tag_text != '') { const SAVEDSEARCH = 6;
$tag_text .= ','; const CONVERSATION = 7;
} /**
* An implicit mention is a mention in a comment body that is redundant with the threading information.
*/
const IMPLICIT_MENTION = 8;
/**
* An exclusive mention transfers the ownership of the post to the target account, usually a forum.
*/
const EXCLUSIVE_MENTION = 9;
if ($tag['type'] == 1) { const TAG_CHARACTER = [
$tag_text .= '#'; self::HASHTAG => '#',
} else { self::MENTION => '@',
$tag_text .= '@'; self::IMPLICIT_MENTION => '%',
} self::EXCLUSIVE_MENTION => '!',
$tag_text .= '[url=' . $tag['url'] . ']' . $tag['term'] . '[/url]'; ];
const OBJECT_TYPE_POST = 1;
const OBJECT_TYPE_PHOTO = 2;
/**
* Generates the legacy item.tag field comma-separated BBCode string from an item ID.
* Includes only hashtags, implicit and explicit mentions.
*
* @param int $item_id
* @return string
* @throws \Exception
*/
public static function tagTextFromItemId($item_id)
{
$tag_list = [];
$tags = self::tagArrayFromItemId($item_id, [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION]);
foreach ($tags as $tag) {
$tag_list[] = self::TAG_CHARACTER[$tag['type']] . '[url=' . $tag['url'] . ']' . $tag['term'] . '[/url]';
} }
return $tag_text;
return implode(',', $tag_list);
} }
public static function tagArrayFromItemId($itemid, $type = [TERM_HASHTAG, TERM_MENTION]) /**
* Retrieves the terms from the provided type(s) associated with the provided item ID.
*
* @param int $item_id
* @param int|array $type
* @return array
* @throws \Exception
*/
public static function tagArrayFromItemId($item_id, $type = [self::HASHTAG, self::MENTION])
{ {
$condition = ['otype' => TERM_OBJ_POST, 'oid' => $itemid, 'type' => $type]; $condition = ['otype' => self::OBJECT_TYPE_POST, 'oid' => $item_id, 'type' => $type];
$tags = DBA::select('term', ['type', 'term', 'url'], $condition); $tags = DBA::select('term', ['type', 'term', 'url'], $condition);
if (!DBA::isResult($tags)) { if (!DBA::isResult($tags)) {
return []; return [];
@ -40,22 +85,39 @@ class Term
return DBA::toArray($tags); return DBA::toArray($tags);
} }
public static function fileTextFromItemId($itemid) /**
* Generates the legacy item.file field string from an item ID.
* Includes only file and category terms.
*
* @param int $item_id
* @return string
* @throws \Exception
*/
public static function fileTextFromItemId($item_id)
{ {
$file_text = ''; $file_text = '';
$condition = ['otype' => TERM_OBJ_POST, 'oid' => $itemid, 'type' => [TERM_FILE, TERM_CATEGORY]]; $tags = self::tagArrayFromItemId($item_id, [self::FILE, self::CATEGORY]);
$tags = DBA::select('term', [], $condition); foreach ($tags as $tag) {
while ($tag = DBA::fetch($tags)) { if ($tag['type'] == self::CATEGORY) {
if ($tag['type'] == TERM_CATEGORY) {
$file_text .= '<' . $tag['term'] . '>'; $file_text .= '<' . $tag['term'] . '>';
} else { } else {
$file_text .= '[' . $tag['term'] . ']'; $file_text .= '[' . $tag['term'] . ']';
} }
} }
return $file_text; return $file_text;
} }
public static function insertFromTagFieldByItemId($itemid, $tags) /**
* Inserts new terms for the provided item ID based on the legacy item.tag field BBCode content.
* Deletes all previous tag terms for the same item ID.
* Sets both the item.mention and thread.mentions field flags if a mention concerning the item UID is found.
*
* @param int $item_id
* @param string $tag_str
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function insertFromTagFieldByItemId($item_id, $tag_str)
{ {
$profile_base = System::baseUrl(); $profile_base = System::baseUrl();
$profile_data = parse_url($profile_base); $profile_data = parse_url($profile_base);
@ -64,32 +126,32 @@ class Term
$profile_base_diaspora = $profile_data['host'] . $profile_path . '/u/'; $profile_base_diaspora = $profile_data['host'] . $profile_path . '/u/';
$fields = ['guid', 'uid', 'id', 'edited', 'deleted', 'created', 'received', 'title', 'body', 'parent']; $fields = ['guid', 'uid', 'id', 'edited', 'deleted', 'created', 'received', 'title', 'body', 'parent'];
$message = Item::selectFirst($fields, ['id' => $itemid]); $item = Item::selectFirst($fields, ['id' => $item_id]);
if (!DBA::isResult($message)) { if (!DBA::isResult($item)) {
return; return;
} }
$message['tag'] = $tags; $item['tag'] = $tag_str;
// Clean up all tags // Clean up all tags
self::deleteByItemId($itemid); self::deleteByItemId($item_id);
if ($message['deleted']) { if ($item['deleted']) {
return; return;
} }
$taglist = explode(',', $message['tag']); $taglist = explode(',', $item['tag']);
$tags_string = ''; $tags_string = '';
foreach ($taglist as $tag) { foreach ($taglist as $tag) {
if ((substr(trim($tag), 0, 1) == '#') || (substr(trim($tag), 0, 1) == '@') || (substr(trim($tag), 0, 1) == '!')) { if (Strings::startsWith($tag, self::TAG_CHARACTER)) {
$tags_string .= ' ' . trim($tag); $tags_string .= ' ' . trim($tag);
} else { } else {
$tags_string .= ' #' . trim($tag); $tags_string .= ' #' . trim($tag);
} }
} }
$data = ' ' . $message['title'] . ' ' . $message['body'] . ' ' . $tags_string . ' '; $data = ' ' . $item['title'] . ' ' . $item['body'] . ' ' . $tags_string . ' ';
// ignore anything in a code block // ignore anything in a code block
$data = preg_replace('/\[code\](.*?)\[\/code\]/sm', '', $data); $data = preg_replace('/\[code\](.*?)\[\/code\]/sm', '', $data);
@ -103,11 +165,15 @@ class Term
} }
} }
$pattern = '/\W([\#@!])\[url\=(.*?)\](.*?)\[\/url\]/ism'; $pattern = '/\W([\#@!%])\[url\=(.*?)\](.*?)\[\/url\]/ism';
if (preg_match_all($pattern, $data, $matches, PREG_SET_ORDER)) { if (preg_match_all($pattern, $data, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) { foreach ($matches as $match) {
if (($match[1] == '@') || ($match[1] == '!')) { if (in_array($match[1], [
self::TAG_CHARACTER[self::MENTION],
self::TAG_CHARACTER[self::IMPLICIT_MENTION],
self::TAG_CHARACTER[self::EXCLUSIVE_MENTION]
])) {
$contact = Contact::getDetailsByURL($match[2], 0); $contact = Contact::getDetailsByURL($match[2], 0);
if (!empty($contact['addr'])) { if (!empty($contact['addr'])) {
$match[3] = $contact['addr']; $match[3] = $contact['addr'];
@ -118,12 +184,12 @@ class Term
} }
} }
$tags[$match[1] . trim($match[3], ',.:;[]/\"?!')] = $match[2]; $tags[$match[2]] = $match[1] . trim($match[3], ',.:;[]/\"?!');
} }
} }
foreach ($tags as $tag => $link) { foreach ($tags as $link => $tag) {
if (substr(trim($tag), 0, 1) == '#') { if (self::isType($tag, self::HASHTAG)) {
// try to ignore #039 or #1 or anything like that // try to ignore #039 or #1 or anything like that
if (ctype_digit(substr(trim($tag), 1))) { if (ctype_digit(substr(trim($tag), 1))) {
continue; continue;
@ -134,11 +200,15 @@ class Term
continue; continue;
} }
$type = TERM_HASHTAG; $type = self::HASHTAG;
$term = substr($tag, 1); $term = substr($tag, 1);
$link = ''; $link = '';
} elseif ((substr(trim($tag), 0, 1) == '@') || (substr(trim($tag), 0, 1) == '!')) { } elseif (self::isType($tag, self::MENTION, self::EXCLUSIVE_MENTION, self::IMPLICIT_MENTION)) {
$type = TERM_MENTION; if (self::isType($tag, self::MENTION, self::EXCLUSIVE_MENTION)) {
$type = self::MENTION;
} else {
$type = self::IMPLICIT_MENTION;
}
$contact = Contact::getDetailsByURL($link, 0); $contact = Contact::getDetailsByURL($link, 0);
if (!empty($contact['name'])) { if (!empty($contact['name'])) {
@ -147,43 +217,51 @@ class Term
$term = substr($tag, 1); $term = substr($tag, 1);
} }
} else { // This shouldn't happen } else { // This shouldn't happen
$type = TERM_HASHTAG; $type = self::HASHTAG;
$term = $tag; $term = $tag;
$link = ''; $link = '';
Logger::notice('Unknown term type', ['tag' => $tag]);
} }
if (DBA::exists('term', ['uid' => $message['uid'], 'otype' => TERM_OBJ_POST, 'oid' => $itemid, 'term' => $term])) { if (DBA::exists('term', ['uid' => $item['uid'], 'otype' => self::OBJECT_TYPE_POST, 'oid' => $item_id, 'term' => $term, 'type' => $type])) {
continue; continue;
} }
if ($message['uid'] == 0) { if ($item['uid'] == 0) {
$global = true; $global = true;
DBA::update('term', ['global' => true], ['otype' => TERM_OBJ_POST, 'guid' => $message['guid']]); DBA::update('term', ['global' => true], ['otype' => self::OBJECT_TYPE_POST, 'guid' => $item['guid']]);
} else { } else {
$global = DBA::exists('term', ['uid' => 0, 'otype' => TERM_OBJ_POST, 'guid' => $message['guid']]); $global = DBA::exists('term', ['uid' => 0, 'otype' => self::OBJECT_TYPE_POST, 'guid' => $item['guid']]);
} }
DBA::insert('term', [ DBA::insert('term', [
'uid' => $message['uid'], 'uid' => $item['uid'],
'oid' => $itemid, 'oid' => $item_id,
'otype' => TERM_OBJ_POST, 'otype' => self::OBJECT_TYPE_POST,
'type' => $type, 'type' => $type,
'term' => $term, 'term' => $term,
'url' => $link, 'url' => $link,
'guid' => $message['guid'], 'guid' => $item['guid'],
'created' => $message['created'], 'created' => $item['created'],
'received' => $message['received'], 'received' => $item['received'],
'global' => $global 'global' => $global
]); ]);
// Search for mentions // Search for mentions
if (((substr($tag, 0, 1) == '@') || (substr($tag, 0, 1) == '!')) && (strpos($link, $profile_base_friendica) || strpos($link, $profile_base_diaspora))) { if (self::isType($tag, self::MENTION, self::EXCLUSIVE_MENTION)
$users = q("SELECT `uid` FROM `contact` WHERE self AND (`url` = '%s' OR `nurl` = '%s')", $link, $link); && (
strpos($link, $profile_base_friendica) !== false
|| strpos($link, $profile_base_diaspora) !== false
)
) {
$users_stmt = DBA::p("SELECT `uid` FROM `contact` WHERE self AND (`url` = ? OR `nurl` = ?)", $link, $link);
$users = DBA::toArray($users_stmt);
foreach ($users AS $user) { foreach ($users AS $user) {
if ($user['uid'] == $message['uid']) { if ($user['uid'] == $item['uid']) {
/// @todo This function is called frim Item::update - so we mustn't call that function here /// @todo This function is called from Item::update - so we mustn't call that function here
DBA::update('item', ['mention' => true], ['id' => $itemid]); DBA::update('item', ['mention' => true], ['id' => $item_id]);
DBA::update('thread', ['mention' => true], ['iid' => $message['parent']]); DBA::update('thread', ['mention' => true], ['iid' => $item['parent']]);
} }
} }
} }
@ -191,20 +269,23 @@ class Term
} }
/** /**
* @param integer $itemid item id * Inserts new terms for the provided item ID based on the legacy item.file field BBCode content.
* Deletes all previous file terms for the same item ID.
*
* @param integer $item_id item id
* @param $files * @param $files
* @return void * @return void
* @throws \Exception * @throws \Exception
*/ */
public static function insertFromFileFieldByItemId($itemid, $files) public static function insertFromFileFieldByItemId($item_id, $files)
{ {
$message = Item::selectFirst(['uid', 'deleted'], ['id' => $itemid]); $message = Item::selectFirst(['uid', 'deleted'], ['id' => $item_id]);
if (!DBA::isResult($message)) { if (!DBA::isResult($message)) {
return; return;
} }
// Clean up all tags // Clean up all tags
DBA::delete('term', ['otype' => TERM_OBJ_POST, 'oid' => $itemid, 'type' => [TERM_FILE, TERM_CATEGORY]]); DBA::delete('term', ['otype' => self::OBJECT_TYPE_POST, 'oid' => $item_id, 'type' => [self::FILE, self::CATEGORY]]);
if ($message["deleted"]) { if ($message["deleted"]) {
return; return;
@ -216,9 +297,9 @@ class Term
foreach ($files[1] as $file) { foreach ($files[1] as $file) {
DBA::insert('term', [ DBA::insert('term', [
'uid' => $message["uid"], 'uid' => $message["uid"],
'oid' => $itemid, 'oid' => $item_id,
'otype' => TERM_OBJ_POST, 'otype' => self::OBJECT_TYPE_POST,
'type' => TERM_FILE, 'type' => self::FILE,
'term' => $file 'term' => $file
]); ]);
} }
@ -228,9 +309,9 @@ class Term
foreach ($files[1] as $file) { foreach ($files[1] as $file) {
DBA::insert('term', [ DBA::insert('term', [
'uid' => $message["uid"], 'uid' => $message["uid"],
'oid' => $itemid, 'oid' => $item_id,
'otype' => TERM_OBJ_POST, 'otype' => self::OBJECT_TYPE_POST,
'type' => TERM_CATEGORY, 'type' => self::CATEGORY,
'term' => $file 'term' => $file
]); ]);
} }
@ -252,6 +333,7 @@ class Term
'tags' => [], 'tags' => [],
'hashtags' => [], 'hashtags' => [],
'mentions' => [], 'mentions' => [],
'implicit_mentions' => [],
]; ];
$searchpath = System::baseUrl() . "/search?tag="; $searchpath = System::baseUrl() . "/search?tag=";
@ -259,10 +341,9 @@ class Term
$taglist = DBA::select( $taglist = DBA::select(
'term', 'term',
['type', 'term', 'url'], ['type', 'term', 'url'],
["`otype` = ? AND `oid` = ? AND `type` IN (?, ?)", TERM_OBJ_POST, $item['id'], TERM_HASHTAG, TERM_MENTION], ['otype' => self::OBJECT_TYPE_POST, 'oid' => $item['id'], 'type' => [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION]],
['order' => ['tid']] ['order' => ['tid']]
); );
while ($tag = DBA::fetch($taglist)) { while ($tag = DBA::fetch($taglist)) {
if ($tag['url'] == '') { if ($tag['url'] == '') {
$tag['url'] = $searchpath . rawurlencode($tag['term']); $tag['url'] = $searchpath . rawurlencode($tag['term']);
@ -270,24 +351,25 @@ class Term
$orig_tag = $tag['url']; $orig_tag = $tag['url'];
$author = ['uid' => 0, 'id' => $item['author-id'], $prefix = self::TAG_CHARACTER[$tag['type']];
'network' => $item['author-network'], 'url' => $item['author-link']]; switch($tag['type']) {
$tag['url'] = Contact::magicLinkByContact($author, $tag['url']); case self::HASHTAG:
if ($orig_tag != $tag['url']) {
$item['body'] = str_replace($orig_tag, $tag['url'], $item['body']);
}
$prefix = ''; $return['hashtags'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank">' . $tag['term'] . '</a>';
if ($tag['type'] == TERM_HASHTAG) { $return['tags'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank">' . $tag['term'] . '</a>';
if ($orig_tag != $tag['url']) { break;
$item['body'] = str_replace($orig_tag, $tag['url'], $item['body']); case self::MENTION:
} $tag['url'] = Contact::magicLink($tag['url']);
$return['mentions'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank">' . $tag['term'] . '</a>';
$return['hashtags'][] = '#<a href="' . $tag['url'] . '" target="_blank">' . $tag['term'] . '</a>'; $return['tags'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank">' . $tag['term'] . '</a>';
$prefix = '#'; break;
} elseif ($tag['type'] == TERM_MENTION) { case self::IMPLICIT_MENTION:
$return['mentions'][] = '@<a href="' . $tag['url'] . '" target="_blank">' . $tag['term'] . '</a>'; $return['implicit_mentions'][] = $prefix . $tag['term'];
$prefix = '@'; break;
} }
$return['tags'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank">' . $tag['term'] . '</a>';
} }
DBA::close($taglist); DBA::close($taglist);
@ -295,20 +377,38 @@ class Term
} }
/** /**
* Delete all tags from an item * Delete tags of the specific type(s) from an item
* *
* @param int itemid - choose from which item the tags will be removed * @param int $item_id
* @param array $type * @param int|array $type
* @throws \Exception * @throws \Exception
*/ */
public static function deleteByItemId($itemid, $type = [TERM_HASHTAG, TERM_MENTION]) public static function deleteByItemId($item_id, $type = [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION])
{ {
if (empty($itemid)) { if (empty($item_id)) {
return; return;
} }
// Clean up all tags // Clean up all tags
DBA::delete('term', ['otype' => TERM_OBJ_POST, 'oid' => $itemid, 'type' => $type]); DBA::delete('term', ['otype' => self::OBJECT_TYPE_POST, 'oid' => $item_id, 'type' => $type]);
}
/**
* Check if the provided tag is of one of the provided term types.
*
* @param string $tag
* @param int ...$types
* @return bool
*/
public static function isType($tag, ...$types)
{
$tag_chars = [];
foreach ($types as $type) {
if (isset(self::TAG_CHARACTER[$type])) {
$tag_chars[] = self::TAG_CHARACTER[$type];
}
}
return Strings::startsWith($tag, $tag_chars);
} }
} }

350
src/Module/Group.php Normal file
View file

@ -0,0 +1,350 @@
<?php
/**
* @file src/Module/Group.php
*/
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Core\Config;
use Friendica\Core\L10n;
use Friendica\Core\PConfig;
use Friendica\Core\Renderer;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Model;
use Friendica\Util\Strings;
require_once 'boot.php';
class Group extends BaseModule
{
public static function post()
{
$a = self::getApp();
if ($a->isAjax()) {
self::ajaxPost();
}
if (!local_user()) {
notice(L10n::t('Permission denied.'));
$a->internalRedirect();
}
if (($a->argc == 2) && ($a->argv[1] === 'new')) {
BaseModule::checkFormSecurityTokenRedirectOnError('/group/new', 'group_edit');
$name = Strings::escapeTags(trim($_POST['groupname']));
$r = Model\Group::create(local_user(), $name);
if ($r) {
info(L10n::t('Group created.'));
$r = Model\Group::getIdByName(local_user(), $name);
if ($r) {
$a->internalRedirect('group/' . $r);
}
} else {
notice(L10n::t('Could not create group.'));
}
$a->internalRedirect('group');
}
if (($a->argc == 2) && intval($a->argv[1])) {
BaseModule::checkFormSecurityTokenRedirectOnError('/group', 'group_edit');
$group = DBA::selectFirst('group', ['id', 'name'], ['id' => $a->argv[1], 'uid' => local_user()]);
if (!DBA::isResult($group)) {
notice(L10n::t('Group not found.'));
$a->internalRedirect('contact');
}
$groupname = Strings::escapeTags(trim($_POST['groupname']));
if (strlen($groupname) && ($groupname != $group['name'])) {
if (Model\Group::update($group['id'], $groupname)) {
info(L10n::t('Group name changed.'));
}
}
}
}
public static function ajaxPost()
{
try {
$a = self::getApp();
if (!local_user()) {
throw new \Exception(L10n::t('Permission denied.'), 403);
}
// POST /group/123/add/123
// POST /group/123/remove/123
if ($a->argc == 4) {
list($group_id, $command, $contact_id) = array_slice($a->argv, 1);
if (!Model\Group::exists($group_id, local_user())) {
throw new \Exception(L10n::t('Unknown group.'), 404);
}
$contact = DBA::selectFirst('contact', ['pending', 'blocked', 'deleted'], ['id' => $contact_id, 'uid' => local_user()]);
if (!DBA::isResult($contact)) {
throw new \Exception(L10n::t('Contact not found.'), 404);
}
if ($contact['pending']) {
throw new \Exception(L10n::t('Contact is unavailable.'), 400);
}
if ($contact['deleted']) {
throw new \Exception(L10n::t('Contact is deleted.'), 410);
}
switch($command) {
case 'add':
if ($contact['blocked']) {
throw new \Exception(L10n::t('Contact is blocked, unable to add it to a group.'), 400);
}
if (!Model\Group::addMember($group_id, $contact_id)) {
throw new \Exception(L10n::t('Unable to add the contact to the group.'), 500);
}
$message = L10n::t('Contact successfully added to group.');
break;
case 'remove':
if (!Model\Group::removeMember($group_id, $contact_id)) {
throw new \Exception(L10n::t('Unable to remove the contact from the group.'), 500);
}
$message = L10n::t('Contact successfully removed from group.');
break;
default:
throw new \Exception(L10n::t('Unknown group command.'), 400);
}
} else {
throw new \Exception(L10n::t('Bad request.'), 400);
}
notice($message);
System::jsonExit(['status' => 'OK', 'message' => $message]);
} catch (\Exception $e) {
notice($e->getMessage());
System::jsonError($e->getCode(), ['status' => 'error', 'message' => $e->getMessage()]);
}
}
public static function content()
{
$change = false;
if (!local_user()) {
System::httpExit(403);
}
$a = self::getApp();
$a->page['aside'] = Model\Group::sidebarWidget('contact', 'group', 'extended', (($a->argc > 1) ? $a->argv[1] : 'everyone'));
// With no group number provided we jump to the unassigned contacts as a starting point
if ($a->argc == 1) {
$a->internalRedirect('group/none');
}
// Switch to text mode interface if we have more than 'n' contacts or group members
$switchtotext = PConfig::get(local_user(), 'system', 'groupedit_image_limit');
if (is_null($switchtotext)) {
$switchtotext = Config::get('system', 'groupedit_image_limit', 200);
}
$tpl = Renderer::getMarkupTemplate('group_edit.tpl');
$context = [
'$submit' => L10n::t('Save Group'),
'$submit_filter' => L10n::t('Filter'),
];
if (($a->argc == 2) && ($a->argv[1] === 'new')) {
return Renderer::replaceMacros($tpl, $context + [
'$title' => L10n::t('Create a group of contacts/friends.'),
'$gname' => ['groupname', L10n::t('Group Name: '), '', ''],
'$gid' => 'new',
'$form_security_token' => BaseModule::getFormSecurityToken("group_edit"),
]);
}
$nogroup = false;
if (($a->argc == 2) && ($a->argv[1] === 'none')) {
$id = -1;
$nogroup = true;
$group = [
'id' => $id,
'name' => L10n::t('Contacts not in any group'),
];
$members = [];
$preselected = [];
$context = $context + [
'$title' => $group['name'],
'$gname' => ['groupname', L10n::t('Group Name: '), $group['name'], ''],
'$gid' => $id,
'$editable' => 0,
];
}
if (($a->argc == 3) && ($a->argv[1] === 'drop')) {
BaseModule::checkFormSecurityTokenRedirectOnError('/group', 'group_drop', 't');
if (intval($a->argv[2])) {
if (!Model\Group::exists($a->argv[2], local_user())) {
notice(L10n::t('Group not found.'));
$a->internalRedirect('contact');
}
if (Model\Group::remove($a->argv[2])) {
info(L10n::t('Group removed.'));
} else {
notice(L10n::t('Unable to remove group.'));
}
}
$a->internalRedirect('group');
}
if (($a->argc > 2) && intval($a->argv[1]) && intval($a->argv[2])) {
BaseModule::checkFormSecurityTokenForbiddenOnError('group_member_change', 't');
if (DBA::exists('contact', ['id' => $a->argv[2], 'uid' => local_user(), 'self' => false, 'pending' => false, 'blocked' => false])) {
$change = intval($a->argv[2]);
}
}
if (($a->argc > 1) && intval($a->argv[1])) {
$group = DBA::selectFirst('group', ['id', 'name'], ['id' => $a->argv[1], 'uid' => local_user(), 'deleted' => false]);
if (!DBA::isResult($group)) {
notice(L10n::t('Group not found.'));
$a->internalRedirect('contact');
}
$members = Model\Contact::getByGroupId($group['id']);
$preselected = [];
if (count($members)) {
foreach ($members as $member) {
$preselected[] = $member['id'];
}
}
if ($change) {
if (in_array($change, $preselected)) {
Model\Group::removeMember($group['id'], $change);
} else {
Model\Group::addMember($group['id'], $change);
}
$members = Model\Contact::getByGroupId($group['id']);
$preselected = [];
if (count($members)) {
foreach ($members as $member) {
$preselected[] = $member['id'];
}
}
}
$drop_tpl = Renderer::getMarkupTemplate('group_drop.tpl');
$drop_txt = Renderer::replaceMacros($drop_tpl, [
'$id' => $group['id'],
'$delete' => L10n::t('Delete Group'),
'$form_security_token' => BaseModule::getFormSecurityToken("group_drop"),
]);
$context = $context + [
'$title' => $group['name'],
'$gname' => ['groupname', L10n::t('Group Name: '), $group['name'], ''],
'$gid' => $group['id'],
'$drop' => $drop_txt,
'$form_security_token' => BaseModule::getFormSecurityToken('group_edit'),
'$edit_name' => L10n::t('Edit Group Name'),
'$editable' => 1,
];
}
if (!isset($group)) {
System::httpExit(400);
}
$groupeditor = [
'label_members' => L10n::t('Members'),
'members' => [],
'label_contacts' => L10n::t('All Contacts'),
'group_is_empty' => L10n::t('Group is empty'),
'contacts' => [],
];
$sec_token = addslashes(BaseModule::getFormSecurityToken('group_member_change'));
// Format the data of the group members
foreach ($members as $member) {
if ($member['url']) {
$entry = Contact::getContactTemplateVars($member);
$entry['label'] = 'members';
$entry['photo_menu'] = '';
$entry['change_member'] = [
'title' => L10n::t("Remove contact from group"),
'gid' => $group['id'],
'cid' => $member['id'],
'sec_token' => $sec_token
];
$groupeditor['members'][] = $entry;
} else {
Model\Group::removeMember($group['id'], $member['id']);
}
}
if ($nogroup) {
$contacts = Model\Contact::getUngroupedList(local_user());
} else {
$contacts_stmt = DBA::select('contact', [],
['uid' => local_user(), 'pending' => false, 'blocked' => false, 'self' => false],
['order' => ['name']]
);
$contacts = DBA::toArray($contacts_stmt);
$context['$desc'] = L10n::t('Click on a contact to add or remove.');
}
if (DBA::isResult($contacts)) {
// Format the data of the contacts who aren't in the contact group
foreach ($contacts as $member) {
if (!in_array($member['id'], $preselected)) {
$entry = Contact::getContactTemplateVars($member);
$entry['label'] = 'contacts';
if (!$nogroup)
$entry['photo_menu'] = [];
if (!$nogroup) {
$entry['change_member'] = [
'title' => L10n::t("Add contact to group"),
'gid' => $group['id'],
'cid' => $member['id'],
'sec_token' => $sec_token
];
}
$groupeditor['contacts'][] = $entry;
}
}
}
$context['$groupeditor'] = $groupeditor;
// If there are to many contacts we could provide an alternative view mode
$total = count($groupeditor['members']) + count($groupeditor['contacts']);
$context['$shortmode'] = (($switchtotext && ($total > $switchtotext)) ? true : false);
if ($change) {
$tpl = Renderer::getMarkupTemplate('groupeditor.tpl');
echo Renderer::replaceMacros($tpl, $context);
exit();
}
return Renderer::replaceMacros($tpl, $context);
}
}

View file

@ -43,6 +43,10 @@ class Install extends BaseModule
{ {
$a = self::getApp(); $a = self::getApp();
if (!$a->getMode()->isInstall()) {
Core\System::httpExit(403);
}
// route: install/testrwrite // route: install/testrwrite
// $baseurl/install/testrwrite to test if rewrite in .htaccess is working // $baseurl/install/testrwrite to test if rewrite in .htaccess is working
if ($a->getArgumentValue(1, '') == 'testrewrite') { if ($a->getArgumentValue(1, '') == 'testrewrite') {
@ -75,7 +79,7 @@ class Install extends BaseModule
$dbdata = Strings::escapeTags(trim(defaults($_POST, 'dbdata', ''))); $dbdata = Strings::escapeTags(trim(defaults($_POST, 'dbdata', '')));
// If we cannot connect to the database, return to the previous step // If we cannot connect to the database, return to the previous step
if (!self::$installer->checkDB($a->getConfig(), $dbhost, $dbuser, $dbpass, $dbdata)) { if (!self::$installer->checkDB($a->getBasePath(), $a->getConfigCache(), $a->getProfiler(), $dbhost, $dbuser, $dbpass, $dbdata)) {
self::$currentWizardStep = self::DATABASE_CONFIG; self::$currentWizardStep = self::DATABASE_CONFIG;
} }
@ -92,7 +96,7 @@ class Install extends BaseModule
$adminmail = Strings::escapeTags(trim(defaults($_POST, 'adminmail', ''))); $adminmail = Strings::escapeTags(trim(defaults($_POST, 'adminmail', '')));
// If we cannot connect to the database, return to the Database config wizard // If we cannot connect to the database, return to the Database config wizard
if (!self::$installer->checkDB($a->getConfig(), $dbhost, $dbuser, $dbpass, $dbdata)) { if (!self::$installer->checkDB($a->getBasePath(), $a->getConfigCache(), $a->getProfiler(), $dbhost, $dbuser, $dbpass, $dbdata)) {
self::$currentWizardStep = self::DATABASE_CONFIG; self::$currentWizardStep = self::DATABASE_CONFIG;
return; return;
} }

View file

@ -2,9 +2,12 @@
namespace Friendica\Module; namespace Friendica\Module;
use Friendica\Content\Text\HTML;
use Friendica\Core\L10n; use Friendica\Core\L10n;
use Friendica\Core\Renderer; use Friendica\Core\Renderer;
use Friendica\Model; use Friendica\Model;
use Friendica\Protocol\ActivityPub\Processor;
use Friendica\Protocol\Diaspora;
/** /**
* @author Hypolite Petovan <mrpetovan@gmail.com> * @author Hypolite Petovan <mrpetovan@gmail.com>
@ -27,20 +30,27 @@ class Itemsource extends \Friendica\BaseModule
$source = ''; $source = '';
$item_uri = ''; $item_uri = '';
$item_id = '';
$terms = [];
if (!empty($guid)) { if (!empty($guid)) {
$item = Model\Item::selectFirst([], ['guid' => $guid]); $item = Model\Item::selectFirst(['id', 'guid', 'uri'], ['guid' => $guid]);
$conversation = Model\Conversation::getByItemUri($item['uri']); $conversation = Model\Conversation::getByItemUri($item['uri']);
$guid = $item['guid'];
$item_id = $item['id'];
$item_uri = $item['uri']; $item_uri = $item['uri'];
$source = $conversation['source']; $source = $conversation['source'];
$terms = Model\Term::tagArrayFromItemId($item['id'], [Model\Term::HASHTAG, Model\Term::MENTION, Model\Term::IMPLICIT_MENTION]);
} }
$tpl = Renderer::getMarkupTemplate('debug/itemsource.tpl'); $tpl = Renderer::getMarkupTemplate('debug/itemsource.tpl');
$o = Renderer::replaceMacros($tpl, [ $o = Renderer::replaceMacros($tpl, [
'$guid' => ['guid', L10n::t('Item Guid'), defaults($_REQUEST, 'guid', ''), ''], '$guid' => ['guid', L10n::t('Item Guid'), $guid, ''],
'$source' => $source, '$source' => $source,
'$item_uri' => $item_uri '$item_uri' => $item_uri,
'$item_id' => $item_id,
'$terms' => $terms,
]); ]);
return $o; return $o;

View file

@ -156,7 +156,7 @@ class Probe
continue; continue;
} }
if (($attributes["rel"] == "lrdd") && !empty($attributes["template"])) { if (!empty($attributes["rel"]) && $attributes["rel"] == "lrdd" && !empty($attributes["template"])) {
$type = (empty($attributes["type"]) ? '' : $attributes["type"]); $type = (empty($attributes["type"]) ? '' : $attributes["type"]);
$lrdd[$type] = $attributes["template"]; $lrdd[$type] = $attributes["template"];

View file

@ -5,6 +5,7 @@
*/ */
namespace Friendica\Object; namespace Friendica\Object;
use Exception;
use Friendica\App; use Friendica\App;
use Friendica\Core\Cache; use Friendica\Core\Cache;
use Friendica\Core\Config; use Friendica\Core\Config;
@ -14,7 +15,6 @@ use Friendica\Core\System;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\Model\Photo; use Friendica\Model\Photo;
use Friendica\Util\Network; use Friendica\Util\Network;
use Exception;
use Imagick; use Imagick;
use ImagickPixel; use ImagickPixel;
@ -656,7 +656,7 @@ class Image
$stamp1 = microtime(true); $stamp1 = microtime(true);
file_put_contents($path, $string); file_put_contents($path, $string);
$a->saveTimestamp($stamp1, "file"); $a->getProfiler()->saveTimestamp($stamp1, "file", System::callstack());
} }
/** /**
@ -802,7 +802,7 @@ class Image
$a = \get_app(); $a = \get_app();
$stamp1 = microtime(true); $stamp1 = microtime(true);
file_put_contents($tempfile, $img_str); file_put_contents($tempfile, $img_str);
$a->saveTimestamp($stamp1, "file"); $a->getProfiler()->saveTimestamp($stamp1, "file", System::callstack());
$data = getimagesize($tempfile); $data = getimagesize($tempfile);
unlink($tempfile); unlink($tempfile);
@ -910,7 +910,7 @@ class Image
$stamp1 = microtime(true); $stamp1 = microtime(true);
$imagedata = @file_get_contents($url); $imagedata = @file_get_contents($url);
$a->saveTimestamp($stamp1, "file"); $a->getProfiler()->saveTimestamp($stamp1, "file", System::callstack());
} }
$maximagesize = Config::get('system', 'maximagesize'); $maximagesize = Config::get('system', 'maximagesize');
@ -924,7 +924,7 @@ class Image
$stamp1 = microtime(true); $stamp1 = microtime(true);
file_put_contents($tempfile, $imagedata); file_put_contents($tempfile, $imagedata);
$a->saveTimestamp($stamp1, "file"); $a->getProfiler()->saveTimestamp($stamp1, "file", System::callstack());
$data = getimagesize($tempfile); $data = getimagesize($tempfile);

View file

@ -19,6 +19,7 @@ use Friendica\Database\DBA;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Friendica\Model\Item; use Friendica\Model\Item;
use Friendica\Model\Term; use Friendica\Model\Term;
use Friendica\Model\User;
use Friendica\Util\Crypto; use Friendica\Util\Crypto;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
use Friendica\Util\Proxy as ProxyUtils; use Friendica\Util\Proxy as ProxyUtils;
@ -82,7 +83,7 @@ class Post extends BaseObject
$author = ['uid' => 0, 'id' => $this->getDataValue('author-id'), $author = ['uid' => 0, 'id' => $this->getDataValue('author-id'),
'network' => $this->getDataValue('author-network'), 'network' => $this->getDataValue('author-network'),
'url' => $this->getDataValue('author-link')]; 'url' => $this->getDataValue('author-link')];
$this->redirect_url = Contact::magicLinkbyContact($author); $this->redirect_url = Contact::magicLinkByContact($author);
if (!$this->isToplevel()) { if (!$this->isToplevel()) {
$this->threaded = true; $this->threaded = true;
} }
@ -223,7 +224,7 @@ class Post extends BaseObject
'network' => $item['author-network'], 'url' => $item['author-link']]; 'network' => $item['author-network'], 'url' => $item['author-link']];
if (local_user() || remote_user()) { if (local_user() || remote_user()) {
$profile_link = Contact::magicLinkbyContact($author); $profile_link = Contact::magicLinkByContact($author);
} else { } else {
$profile_link = $item['author-link']; $profile_link = $item['author-link'];
} }
@ -365,6 +366,7 @@ class Post extends BaseObject
'tags' => $tags['tags'], 'tags' => $tags['tags'],
'hashtags' => $tags['hashtags'], 'hashtags' => $tags['hashtags'],
'mentions' => $tags['mentions'], 'mentions' => $tags['mentions'],
'implicit_mentions' => $tags['implicit_mentions'],
'txt_cats' => L10n::t('Categories:'), 'txt_cats' => L10n::t('Categories:'),
'txt_folders' => L10n::t('Filed under:'), 'txt_folders' => L10n::t('Filed under:'),
'has_cats' => ((count($categories)) ? 'true' : ''), 'has_cats' => ((count($categories)) ? 'true' : ''),
@ -415,6 +417,7 @@ class Post extends BaseObject
'dislike' => $responses['dislike']['output'], 'dislike' => $responses['dislike']['output'],
'responses' => $responses, 'responses' => $responses,
'switchcomment' => L10n::t('Comment'), 'switchcomment' => L10n::t('Comment'),
'reply_label' => L10n::t('Reply to %s', $name_e),
'comment' => $comment, 'comment' => $comment,
'previewing' => $conv->isPreview() ? ' preview ' : '', 'previewing' => $conv->isPreview() ? ' preview ' : '',
'wait' => L10n::t('Please wait'), 'wait' => L10n::t('Please wait'),
@ -449,13 +452,13 @@ class Post extends BaseObject
foreach ($children as $child) { foreach ($children as $child) {
$result['children'][] = $child->getTemplateData($conv_responses, $thread_level + 1); $result['children'][] = $child->getTemplateData($conv_responses, $thread_level + 1);
} }
// Collapse // Collapse
if (($nb_children > 2) || ($thread_level > 1)) { if (($nb_children > 2) || ($thread_level > 1)) {
$result['children'][0]['comment_firstcollapsed'] = true; $result['children'][0]['comment_firstcollapsed'] = true;
$result['children'][0]['num_comments'] = L10n::tt('%d comment', '%d comments', $total_children); $result['children'][0]['num_comments'] = L10n::tt('%d comment', '%d comments', $total_children);
$result['children'][0]['hidden_comments_num'] = $total_children; $result['children'][0]['show_text'] = L10n::t('Show more');
$result['children'][0]['hidden_comments_text'] = L10n::tt('comment', 'comments', $total_children); $result['children'][0]['hide_text'] = L10n::t('Show fewer');
$result['children'][0]['hide_text'] = L10n::t('show more');
if ($thread_level > 1) { if ($thread_level > 1) {
$result['children'][$nb_children - 1]['comment_lastcollapsed'] = true; $result['children'][$nb_children - 1]['comment_lastcollapsed'] = true;
} else { } else {
@ -780,10 +783,12 @@ class Post extends BaseObject
{ {
$a = self::getApp(); $a = self::getApp();
if (!local_user() || empty($a->profile['addr'])) { if (!local_user()) {
return ''; return '';
} }
$owner = User::getOwnerDataById($a->user['uid']);
if (!Feature::isEnabled(local_user(), 'explicit_mentions')) { if (!Feature::isEnabled(local_user(), 'explicit_mentions')) {
return ''; return '';
} }
@ -794,18 +799,18 @@ class Post extends BaseObject
return ''; return '';
} }
if ($item['author-addr'] != $a->profile['addr']) { if ($item['author-addr'] != $owner['addr']) {
$text = '@' . $item['author-addr'] . ' '; $text = '@' . $item['author-addr'] . ' ';
} else { } else {
$text = ''; $text = '';
} }
$terms = Term::tagArrayFromItemId($this->getId(), TERM_MENTION); $terms = Term::tagArrayFromItemId($this->getId(), [Term::MENTION, Term::IMPLICIT_MENTION]);
foreach ($terms as $term) { foreach ($terms as $term) {
$profile = Contact::getDetailsByURL($term['url']); $profile = Contact::getDetailsByURL($term['url']);
if (!empty($profile['addr']) && ($profile['contact-type'] != Contact::TYPE_COMMUNITY) && if (!empty($profile['addr']) && !empty($profile['contact-type']) && ($profile['contact-type'] != Contact::TYPE_COMMUNITY) &&
($profile['addr'] != $a->profile['addr']) && !strstr($text, $profile['addr'])) { ($profile['addr'] != $owner['addr']) && !strstr($text, $profile['addr'])) {
$text .= '@' . $profile['addr'] . ' '; $text .= '@' . $profile['addr'] . ' ';
} }
} }
@ -944,7 +949,7 @@ class Post extends BaseObject
$owner = ['uid' => 0, 'id' => $this->getDataValue('owner-id'), $owner = ['uid' => 0, 'id' => $this->getDataValue('owner-id'),
'network' => $this->getDataValue('owner-network'), 'network' => $this->getDataValue('owner-network'),
'url' => $this->getDataValue('owner-link')]; 'url' => $this->getDataValue('owner-link')];
$this->owner_url = Contact::magicLinkbyContact($owner); $this->owner_url = Contact::magicLinkByContact($owner);
} }
} }
} }

View file

@ -65,7 +65,7 @@ class Processor
* @param array $implicit_mentions List of profile URLs to skip * @param array $implicit_mentions List of profile URLs to skip
* @return string with tags * @return string with tags
*/ */
private static function constructTagString($tags, $sensitive, array $implicit_mentions) private static function constructTagString(array $tags, $sensitive)
{ {
if (empty($tags)) { if (empty($tags)) {
return ''; return '';
@ -73,7 +73,7 @@ class Processor
$tag_text = ''; $tag_text = '';
foreach ($tags as $tag) { foreach ($tags as $tag) {
if (in_array(defaults($tag, 'type', ''), ['Mention', 'Hashtag']) && !in_array($tag['href'], $implicit_mentions)) { if (in_array(defaults($tag, 'type', ''), ['Mention', 'Hashtag'])) {
if (!empty($tag_text)) { if (!empty($tag_text)) {
$tag_text .= ','; $tag_text .= ',';
} }
@ -129,7 +129,7 @@ class Processor
*/ */
public static function updateItem($activity) public static function updateItem($activity)
{ {
$item = Item::selectFirst(['uri', 'parent-uri', 'gravity'], ['uri' => $activity['id']]); $item = Item::selectFirst(['uri', 'thr-parent', 'gravity'], ['uri' => $activity['id']]);
if (!DBA::isResult($item)) { if (!DBA::isResult($item)) {
Logger::warning('Unknown item', ['uri' => $activity['id']]); Logger::warning('Unknown item', ['uri' => $activity['id']]);
return; return;
@ -144,20 +144,20 @@ class Processor
$content = self::replaceEmojis($content, $activity['emojis']); $content = self::replaceEmojis($content, $activity['emojis']);
$content = self::convertMentions($content); $content = self::convertMentions($content);
$implicit_mentions = []; if (($item['thr-parent'] != $item['uri']) && ($item['gravity'] == GRAVITY_COMMENT)) {
if (($item['parent-uri'] != $item['uri']) && ($item['gravity'] == GRAVITY_COMMENT)) { $parent = Item::selectFirst(['id', 'author-link', 'alias'], ['uri' => $item['thr-parent']]);
$parent = Item::selectFirst(['id', 'author-link', 'alias'], ['uri' => $item['parent-uri']]);
if (!DBA::isResult($parent)) { if (!DBA::isResult($parent)) {
Logger::warning('Unknown parent item.', ['uri' => $item['parent-uri']]); Logger::warning('Unknown parent item.', ['uri' => $item['thr-parent']]);
return; return;
} }
$implicit_mentions = self::getImplicitMentionList($parent); $potential_implicit_mentions = self::getImplicitMentionList($parent);
$content = self::removeImplicitMentionsFromBody($content, $implicit_mentions); $content = self::removeImplicitMentionsFromBody($content, $potential_implicit_mentions);
$activity['tags'] = self::convertImplicitMentionsInTags($activity['tags'], $potential_implicit_mentions);
} }
$item['body'] = $content; $item['body'] = $content;
$item['tag'] = self::constructTagString($activity['tags'], $activity['sensitive'], $implicit_mentions); $item['tag'] = self::constructTagString($activity['tags'], $activity['sensitive']);
Item::update($item, ['uri' => $activity['id']]); Item::update($item, ['uri' => $activity['id']]);
} }
@ -173,7 +173,7 @@ class Processor
{ {
$item = []; $item = [];
$item['verb'] = ACTIVITY_POST; $item['verb'] = ACTIVITY_POST;
$item['parent-uri'] = $activity['reply-to-id']; $item['thr-parent'] = $activity['reply-to-id'];
if ($activity['reply-to-id'] == $activity['id']) { if ($activity['reply-to-id'] == $activity['id']) {
$item['gravity'] = GRAVITY_PARENT; $item['gravity'] = GRAVITY_PARENT;
@ -220,7 +220,7 @@ class Processor
{ {
$item = []; $item = [];
$item['verb'] = $verb; $item['verb'] = $verb;
$item['parent-uri'] = $activity['object_id']; $item['thr-parent'] = $activity['object_id'];
$item['gravity'] = GRAVITY_ACTIVITY; $item['gravity'] = GRAVITY_ACTIVITY;
$item['object-type'] = ACTIVITY_OBJ_NOTE; $item['object-type'] = ACTIVITY_OBJ_NOTE;
@ -275,8 +275,8 @@ class Processor
{ {
/// @todo What to do with $activity['context']? /// @todo What to do with $activity['context']?
if (($item['gravity'] != GRAVITY_PARENT) && !Item::exists(['uri' => $item['parent-uri']])) { if (($item['gravity'] != GRAVITY_PARENT) && !Item::exists(['uri' => $item['thr-parent']])) {
Logger::log('Parent ' . $item['parent-uri'] . ' not found, message will be discarded.', Logger::DEBUG); Logger::info('Parent not found, message will be discarded.', ['thr-parent' => $item['thr-parent']]);
return; return;
} }
@ -289,7 +289,7 @@ class Processor
$item['owner-link'] = $activity['actor']; $item['owner-link'] = $activity['actor'];
$item['owner-id'] = Contact::getIdForURL($activity['actor'], 0, true); $item['owner-id'] = Contact::getIdForURL($activity['actor'], 0, true);
} else { } else {
Logger::log('Ignoring actor because of thread completion.', Logger::DEBUG); Logger::info('Ignoring actor because of thread completion.');
$item['owner-link'] = $item['author-link']; $item['owner-link'] = $item['author-link'];
$item['owner-id'] = $item['author-id']; $item['owner-id'] = $item['author-id'];
} }
@ -299,21 +299,20 @@ class Processor
$content = self::replaceEmojis($content, $activity['emojis']); $content = self::replaceEmojis($content, $activity['emojis']);
$content = self::convertMentions($content); $content = self::convertMentions($content);
$implicit_mentions = []; if (($item['thr-parent'] != $item['uri']) && ($item['gravity'] == GRAVITY_COMMENT)) {
if (($item['parent-uri'] != $item['uri']) && ($item['gravity'] == GRAVITY_COMMENT)) {
$item_private = !in_array(0, $activity['item_receiver']); $item_private = !in_array(0, $activity['item_receiver']);
$parent = Item::selectFirst(['id', 'private', 'author-link', 'alias'], ['uri' => $item['parent-uri']]); $parent = Item::selectFirst(['id', 'private', 'author-link', 'alias'], ['uri' => $item['thr-parent']]);
if (!DBA::isResult($parent)) { if (!DBA::isResult($parent)) {
return; return;
} }
if ($item_private && !$parent['private']) { if ($item_private && !$parent['private']) {
Logger::log('Item ' . $item['uri'] . ' is private but the parent ' . $item['parent-uri'] . ' is not. So we drop it.'); Logger::warning('Item is private but the parent is not. Dropping.', ['item-uri' => $item['uri'], 'thr-parent' => $item['thr-parent']]);
return; return;
} }
$implicit_mentions = self::getImplicitMentionList($parent); $potential_implicit_mentions = self::getImplicitMentionList($parent);
$content = self::removeImplicitMentionsFromBody($content, $implicit_mentions); $content = self::removeImplicitMentionsFromBody($content, $potential_implicit_mentions);
$activity['tags'] = self::convertImplicitMentionsInTags($activity['tags'], $potential_implicit_mentions);
} }
$item['created'] = $activity['published']; $item['created'] = $activity['published'];
@ -333,7 +332,7 @@ class Processor
$item['coord'] = $item['latitude'] . ' ' . $item['longitude']; $item['coord'] = $item['latitude'] . ' ' . $item['longitude'];
} }
$item['tag'] = self::constructTagString($activity['tags'], $activity['sensitive'], $implicit_mentions); $item['tag'] = self::constructTagString($activity['tags'], $activity['sensitive']);
$item['app'] = $activity['generator']; $item['app'] = $activity['generator'];
$item['plink'] = defaults($activity, 'alternate-url', $item['uri']); $item['plink'] = defaults($activity, 'alternate-url', $item['uri']);
@ -358,7 +357,11 @@ class Processor
} }
$item_id = Item::insert($item); $item_id = Item::insert($item);
Logger::log('Storing for user ' . $item['uid'] . ': ' . $item_id); if ($item_id) {
Logger::info('Item insertion successful', ['user' => $item['uid'], 'item_id' => $item_id]);
} else {
Logger::notice('Item insertion aborted', ['user' => $item['uid']]);
}
if ($item['uid'] == 0) { if ($item['uid'] == 0) {
$stored = $item_id; $stored = $item_id;
@ -658,13 +661,24 @@ class Processor
*/ */
private static function getImplicitMentionList(array $parent) private static function getImplicitMentionList(array $parent)
{ {
$parent_terms = Term::tagArrayFromItemId($parent['id'], [TERM_MENTION]); if (Config::get('system', 'disable_implicit_mentions')) {
return [];
}
$implicit_mentions = [ $parent_terms = Term::tagArrayFromItemId($parent['id'], [Term::MENTION, Term::IMPLICIT_MENTION]);
$parent['author-link']
];
if ($parent['alias']) { $parent_author = Contact::getDetailsByURL($parent['author-link'], 0);
$implicit_mentions = [];
if (empty($parent_author)) {
Logger::notice('Author public contact unknown.', ['author-link' => $parent['author-link'], 'item-id' => $parent['id']]);
} else {
$implicit_mentions[] = $parent_author['url'];
$implicit_mentions[] = $parent_author['nurl'];
$implicit_mentions[] = $parent_author['alias'];
}
if (!empty($parent['alias'])) {
$implicit_mentions[] = $parent['alias']; $implicit_mentions[] = $parent['alias'];
} }
@ -684,12 +698,12 @@ class Processor
* Strips from the body prepended implicit mentions * Strips from the body prepended implicit mentions
* *
* @param string $body * @param string $body
* @param array $implicit_mentions List of profile URLs * @param array $potential_mentions
* @return string * @return string
*/ */
private static function removeImplicitMentionsFromBody($body, array $implicit_mentions) private static function removeImplicitMentionsFromBody($body, array $potential_mentions)
{ {
if (Config::get('system', 'disable_mentions_removal')) { if (Config::get('system', 'disable_implicit_mentions')) {
return $body; return $body;
} }
@ -697,7 +711,7 @@ class Processor
// Extract one prepended mention at a time from the body // Extract one prepended mention at a time from the body
while(preg_match('#^(@\[url=([^\]]+)].*?\[\/url]\s)(.*)#mis', $body, $matches)) { while(preg_match('#^(@\[url=([^\]]+)].*?\[\/url]\s)(.*)#mis', $body, $matches)) {
if (!in_array($matches[2], $implicit_mentions) ) { if (!in_array($matches[2], $potential_mentions) ) {
$kept_mentions[] = $matches[1]; $kept_mentions[] = $matches[1];
} }
@ -709,4 +723,50 @@ class Processor
return implode('', $kept_mentions); return implode('', $kept_mentions);
} }
private static function convertImplicitMentionsInTags($activity_tags, array $potential_mentions)
{
if (Config::get('system', 'disable_implicit_mentions')) {
return $activity_tags;
}
foreach ($activity_tags as $index => $tag) {
if (in_array($tag['href'], $potential_mentions)) {
$activity_tags[$index]['name'] = preg_replace(
'/' . preg_quote(Term::TAG_CHARACTER[Term::MENTION], '/') . '/',
Term::TAG_CHARACTER[Term::IMPLICIT_MENTION],
$activity_tags[$index]['name'],
1
);
}
}
return $activity_tags;
}
public static function testImplicitMentions($item, $source)
{
$parent = Item::selectFirst(['id', 'guid', 'author-link', 'alias'], ['uri' => $item['thr-parent']]);
$implicit_mentions = self::getImplicitMentionList($parent);
var_dump($implicit_mentions);
$object = json_decode($source, true)['object'];
var_dump($object);
$content = HTML::toBBCode($object['content']);
$content = self::convertMentions($content);
$activity = [
'tags' => $object['tag'],
'content' => $content
];
var_dump($activity);
$activity['content'] = Processor::removeImplicitMentionsFromBody($activity['content'], $implicit_mentions);
$activity['tags'] = Processor::convertImplicitMentionsInTags($activity['tags'], $implicit_mentions);
return $activity;
}
} }

View file

@ -62,16 +62,16 @@ class Receiver
{ {
$http_signer = HTTPSignature::getSigner($body, $header); $http_signer = HTTPSignature::getSigner($body, $header);
if (empty($http_signer)) { if (empty($http_signer)) {
Logger::log('Invalid HTTP signature, message will be discarded.', Logger::DEBUG); Logger::warning('Invalid HTTP signature, message will be discarded.');
return; return;
} else { } else {
Logger::log('HTTP signature is signed by ' . $http_signer, Logger::DEBUG); Logger::info('Valid HTTP signature', ['signer' => $http_signer]);
} }
$activity = json_decode($body, true); $activity = json_decode($body, true);
if (empty($activity)) { if (empty($activity)) {
Logger::log('Invalid body.', Logger::DEBUG); Logger::warning('Invalid body.');
return; return;
} }
@ -79,7 +79,7 @@ class Receiver
$actor = JsonLD::fetchElement($ldactivity, 'as:actor'); $actor = JsonLD::fetchElement($ldactivity, 'as:actor');
Logger::log('Message for user ' . $uid . ' is from actor ' . $actor, Logger::DEBUG); Logger::info('Message for user ' . $uid . ' is from actor ' . $actor);
if (LDSignature::isSigned($activity)) { if (LDSignature::isSigned($activity)) {
$ld_signer = LDSignature::getSigner($activity); $ld_signer = LDSignature::getSigner($activity);

View file

@ -343,7 +343,7 @@ class Transmitter
$actor_profile = APContact::getByURL($item['author-link']); $actor_profile = APContact::getByURL($item['author-link']);
} }
$terms = Term::tagArrayFromItemId($item['id'], TERM_MENTION); $terms = Term::tagArrayFromItemId($item['id'], [Term::MENTION, Term::IMPLICIT_MENTION]);
if (!$item['private']) { if (!$item['private']) {
$data = array_merge($data, self::fetchPermissionBlockFromConversation($item)); $data = array_merge($data, self::fetchPermissionBlockFromConversation($item));
@ -807,12 +807,12 @@ class Transmitter
{ {
$tags = []; $tags = [];
$terms = Term::tagArrayFromItemId($item['id']); $terms = Term::tagArrayFromItemId($item['id'], [Term::HASHTAG, Term::MENTION, Term::IMPLICIT_MENTION]);
foreach ($terms as $term) { foreach ($terms as $term) {
if ($term['type'] == TERM_HASHTAG) { if ($term['type'] == Term::HASHTAG) {
$url = System::baseUrl() . '/search?tag=' . urlencode($term['term']); $url = System::baseUrl() . '/search?tag=' . urlencode($term['term']);
$tags[] = ['type' => 'Hashtag', 'href' => $url, 'name' => '#' . $term['term']]; $tags[] = ['type' => 'Hashtag', 'href' => $url, 'name' => '#' . $term['term']];
} elseif ($term['type'] == TERM_MENTION) { } elseif ($term['type'] == Term::MENTION || $term['type'] == Term::IMPLICIT_MENTION) {
$contact = Contact::getDetailsByURL($term['url']); $contact = Contact::getDetailsByURL($term['url']);
if (!empty($contact['addr'])) { if (!empty($contact['addr'])) {
$mention = '@' . $contact['addr']; $mention = '@' . $contact['addr'];
@ -1439,6 +1439,10 @@ class Transmitter
private static function prependMentions($body, array $permission_block) private static function prependMentions($body, array $permission_block)
{ {
if (Config::get('system', 'disable_implicit_mentions')) {
return $body;
}
$mentions = []; $mentions = [];
foreach ($permission_block['to'] as $profile_url) { foreach ($permission_block['to'] as $profile_url) {

View file

@ -119,7 +119,9 @@ class DFRN
$item["entry:cid"] = defaults($item, "entry:cid", 0); $item["entry:cid"] = defaults($item, "entry:cid", 0);
$entry = self::entry($doc, "text", $item, $owner, $item["entry:comment-allow"], $item["entry:cid"]); $entry = self::entry($doc, "text", $item, $owner, $item["entry:comment-allow"], $item["entry:cid"]);
$root->appendChild($entry); if (isset($entry)) {
$root->appendChild($entry);
}
} }
return trim($doc->saveXML()); return trim($doc->saveXML());
@ -323,7 +325,9 @@ class DFRN
} }
$entry = self::entry($doc, $type, $item, $owner, true); $entry = self::entry($doc, $type, $item, $owner, true);
$root->appendChild($entry); if (isset($entry)) {
$root->appendChild($entry);
}
} }
$atom = trim($doc->saveXML()); $atom = trim($doc->saveXML());
@ -390,7 +394,9 @@ class DFRN
foreach ($items as $item) { foreach ($items as $item) {
$entry = self::entry($doc, $type, $item, $owner, true, 0); $entry = self::entry($doc, $type, $item, $owner, true, 0);
$root->appendChild($entry); if (isset($entry)) {
$root->appendChild($entry);
}
} }
} else { } else {
$root = self::entry($doc, $type, $item, $owner, true, 0, true); $root = self::entry($doc, $type, $item, $owner, true, 0, true);
@ -763,31 +769,33 @@ class DFRN
*/ */
private static function addEntryAuthor(DOMDocument $doc, $element, $contact_url, $item) private static function addEntryAuthor(DOMDocument $doc, $element, $contact_url, $item)
{ {
$contact = Contact::getDetailsByURL($contact_url, $item["uid"]);
$author = $doc->createElement($element); $author = $doc->createElement($element);
XML::addElement($doc, $author, "name", $contact["name"]);
XML::addElement($doc, $author, "uri", $contact["url"]);
XML::addElement($doc, $author, "dfrn:handle", $contact["addr"]);
/// @Todo $contact = Contact::getDetailsByURL($contact_url, $item["uid"]);
/// - Check real image type and image size if (!empty($contact)) {
/// - Check which of these boths elements we should use XML::addElement($doc, $author, "name", $contact["name"]);
$attributes = [ XML::addElement($doc, $author, "uri", $contact["url"]);
XML::addElement($doc, $author, "dfrn:handle", $contact["addr"]);
/// @Todo
/// - Check real image type and image size
/// - Check which of these boths elements we should use
$attributes = [
"rel" => "photo", "rel" => "photo",
"type" => "image/jpeg", "type" => "image/jpeg",
"media:width" => 80, "media:width" => 80,
"media:height" => 80, "media:height" => 80,
"href" => $contact["photo"]]; "href" => $contact["photo"]];
XML::addElement($doc, $author, "link", "", $attributes); XML::addElement($doc, $author, "link", "", $attributes);
$attributes = [ $attributes = [
"rel" => "avatar", "rel" => "avatar",
"type" => "image/jpeg", "type" => "image/jpeg",
"media:width" => 80, "media:width" => 80,
"media:height" => 80, "media:height" => 80,
"href" => $contact["photo"]]; "href" => $contact["photo"]];
XML::addElement($doc, $author, "link", "", $attributes); XML::addElement($doc, $author, "link", "", $attributes);
}
return $author; return $author;
} }
@ -906,7 +914,7 @@ class DFRN
* @param int $cid Contact ID of the recipient * @param int $cid Contact ID of the recipient
* @param bool $single If set, the entry is created as an XML document with a single "entry" element * @param bool $single If set, the entry is created as an XML document with a single "entry" element
* *
* @return \DOMElement XML entry object * @return null|\DOMElement XML entry object
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException * @throws \ImagickException
* @todo Find proper type-hints * @todo Find proper type-hints
@ -916,7 +924,8 @@ class DFRN
$mentioned = []; $mentioned = [];
if (!$item['parent']) { if (!$item['parent']) {
return; Logger::notice('Item without parent found.', ['type' => $type, 'item' => $item]);
return null;
} }
if ($item['deleted']) { if ($item['deleted']) {
@ -1546,7 +1555,7 @@ class DFRN
$author["network"] = $contact_old["network"]; $author["network"] = $contact_old["network"];
} else { } else {
if (!$onlyfetch) { if (!$onlyfetch) {
Logger::log("Contact ".$author["link"]." wasn't found for user ".$importer["importer_uid"]." XML: ".$xml, Logger::DEBUG); Logger::debug("Contact ".$author["link"]." wasn't found for user ".$importer["importer_uid"]." XML: ".$xml);
} }
$author["contact-unknown"] = true; $author["contact-unknown"] = true;
@ -1596,6 +1605,7 @@ class DFRN
if (empty($author['avatar'])) { if (empty($author['avatar'])) {
Logger::log('Empty author: ' . $xml); Logger::log('Empty author: ' . $xml);
$author['avatar'] = '';
} }
if (DBA::isResult($contact_old) && !$onlyfetch) { if (DBA::isResult($contact_old) && !$onlyfetch) {

View file

@ -942,7 +942,7 @@ class Diaspora
$person = DBA::selectFirst('fcontact', [], ['network' => Protocol::DIASPORA, 'addr' => $handle]); $person = DBA::selectFirst('fcontact', [], ['network' => Protocol::DIASPORA, 'addr' => $handle]);
if (DBA::isResult($person)) { if (DBA::isResult($person)) {
Logger::log("In cache " . print_r($person, true), Logger::DEBUG); Logger::debug("In cache " . print_r($person, true));
// update record occasionally so it doesn't get stale // update record occasionally so it doesn't get stale
$d = strtotime($person["updated"]." +00:00"); $d = strtotime($person["updated"]." +00:00");
@ -3675,7 +3675,7 @@ class Diaspora
&& !strstr($body, $profile['addr']) && !strstr($body, $profile['addr'])
&& !strstr($body, $profile_url) && !strstr($body, $profile_url)
) { ) {
$body = '@[url=' . $profile_url . ']' . $profile['nick'] . '[/url] ' . $body; $body = '@[url=' . $profile_url . ']' . $profile['name'] . '[/url] ' . $body;
} }
return $body; return $body;
@ -3776,7 +3776,7 @@ class Diaspora
* @param array $item The item that will be exported * @param array $item The item that will be exported
* @param array $owner the array of the item owner * @param array $owner the array of the item owner
* *
* @return array The data for a comment * @return array|false The data for a comment
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/ */
private static function constructComment(array $item, array $owner) private static function constructComment(array $item, array $owner)
@ -3788,30 +3788,40 @@ class Diaspora
return $result; return $result;
} }
$parent = Item::selectFirst(['guid', 'author-link'], ['id' => $item["parent"], 'parent' => $item["parent"]]); $toplevel_item = Item::selectFirst(['guid', 'author-link'], ['id' => $item["parent"], 'parent' => $item["parent"]]);
if (!DBA::isResult($parent)) { if (!DBA::isResult($toplevel_item)) {
Logger::error('Missing parent conversation item', ['parent' => $item["parent"]]);
return false; return false;
} }
$thread_parent_item = $toplevel_item;
if ($item['thr-parent'] != $item['parent-uri']) {
$thread_parent_item = Item::selectFirst(['guid', 'author-link'], ['uri' => $item['thr-parent'], 'uid' => $item['uid']]);
}
$body = $item["body"]; $body = $item["body"];
if (empty($item['uid']) || !Feature::isEnabled($item['uid'], 'explicit_mentions')) { if ((empty($item['uid']) || !Feature::isEnabled($item['uid'], 'explicit_mentions'))
$body = self::prependParentAuthorMention($body, $parent['author-link']); && !Config::get('system', 'disable_implicit_mentions')
) {
$body = self::prependParentAuthorMention($body, $thread_parent_item['author-link']);
} }
$text = html_entity_decode(BBCode::toMarkdown($body)); $text = html_entity_decode(BBCode::toMarkdown($body));
$created = DateTimeFormat::utc($item["created"], DateTimeFormat::ATOM); $created = DateTimeFormat::utc($item["created"], DateTimeFormat::ATOM);
$comment = ["author" => self::myHandle($owner), $comment = [
"guid" => $item["guid"], "author" => self::myHandle($owner),
"created_at" => $created, "guid" => $item["guid"],
"parent_guid" => $parent["guid"], "created_at" => $created,
"text" => $text, "parent_guid" => $toplevel_item["guid"],
"author_signature" => ""]; "text" => $text,
"author_signature" => ""
];
// Send the thread parent guid only if it is a threaded comment // Send the thread parent guid only if it is a threaded comment
if ($item['thr-parent'] != $item['parent-uri']) { if ($item['thr-parent'] != $item['parent-uri']) {
$comment['thread_parent_guid'] = self::getGuidFromUri($item['thr-parent'], $item['uid']); $comment['thread_parent_guid'] = $thread_parent_item['guid'];
} }
Cache::set($cachekey, $comment, Cache::QUARTER_HOUR); Cache::set($cachekey, $comment, Cache::QUARTER_HOUR);

View file

@ -1763,20 +1763,17 @@ class OStatus
$verb = NAMESPACE_ACTIVITY_SCHEMA."favorite"; $verb = NAMESPACE_ACTIVITY_SCHEMA."favorite";
self::entryContent($doc, $entry, $item, $owner, "Favorite", $verb, false); self::entryContent($doc, $entry, $item, $owner, "Favorite", $verb, false);
$as_object = $doc->createElement("activity:object");
$parent = Item::selectFirst([], ['uri' => $item["thr-parent"], 'uid' => $item["uid"]]); $parent = Item::selectFirst([], ['uri' => $item["thr-parent"], 'uid' => $item["uid"]]);
if (DBA::isResult($parent)) {
$as_object = $doc->createElement("activity:object");
if (!$parent) { XML::addElement($doc, $as_object, "activity:object-type", self::constructObjecttype($parent));
$parent = [];
self::entryContent($doc, $as_object, $parent, $owner, "New entry");
$entry->appendChild($as_object);
} }
XML::addElement($doc, $as_object, "activity:object-type", self::constructObjecttype($parent));
self::entryContent($doc, $as_object, $parent, $owner, "New entry");
$entry->appendChild($as_object);
self::entryFooter($doc, $entry, $item, $owner); self::entryFooter($doc, $entry, $item, $owner);
return $entry; return $entry;

View file

@ -505,8 +505,15 @@ class PortableContact
$last_updated = ""; $last_updated = "";
foreach ($entries as $entry) { foreach ($entries as $entry) {
$published = DateTimeFormat::utc($xpath->query('atom:published/text()', $entry)->item(0)->nodeValue); $published_item = $xpath->query('atom:published/text()', $entry)->item(0);
$updated = DateTimeFormat::utc($xpath->query('atom:updated/text()' , $entry)->item(0)->nodeValue); $updated_item = $xpath->query('atom:updated/text()' , $entry)->item(0);
$published = isset($published_item->nodeValue) ? DateTimeFormat::utc($published_item->nodeValue) : null;
$updated = isset($updated_item->nodeValue) ? DateTimeFormat::utc($updated_item->nodeValue) : null;
if (!isset($published) || !isset($updated)) {
Logger::notice('Invalid entry for XPath.', ['entry' => $entry, 'profile' => $profile]);
continue;
}
if ($last_updated < $published) { if ($last_updated < $published) {
$last_updated = $published; $last_updated = $published;

View file

@ -68,7 +68,7 @@ class JsonLD
} }
catch (Exception $e) { catch (Exception $e) {
$normalized = false; $normalized = false;
Logger::log('normalise error:' . print_r($e, true), Logger::DEBUG); Logger::log('normalise error:' . substr(print_r($e, true), 0, 10000), Logger::DEBUG);
} }
return $normalized; return $normalized;
@ -115,7 +115,7 @@ class JsonLD
} }
catch (Exception $e) { catch (Exception $e) {
$compacted = false; $compacted = false;
Logger::log('compacting error:' . print_r($e, true), Logger::DEBUG); Logger::log('compacting error:' . substr(print_r($e, true), 0, 10000), Logger::DEBUG);
} }
$json = json_decode(json_encode($compacted, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), true); $json = json_decode(json_encode($compacted, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), true);

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