Merge remote-tracking branch 'upstream/develop' into item-thread

This commit is contained in:
Michael 2018-10-19 03:19:34 +00:00
commit 4ae37c6196
306 changed files with 30395 additions and 24811 deletions

131
CHANGELOG
View file

@ -1,3 +1,134 @@
Version 2018.09 (2018-09-23)
Friendica Core:
Update to the translation (CS, DE, EN-US, FI, IT, NL, PL, ZH-CN) [translation teams]
Update to the documentation [Aditoo17, annando, astifter, rebeka-catalina, fabrixxm, M-arcus, microgroove, nupplaphil, tobiasd]
Enhancements to the database structure, handling and documentation [abanink, Angristan, annando, miqrogroove, tobiasd]
Enhancements of unit testing [abanink, nupplaphil, rudloff]
Enhancements to labelling of UI elements [andyhee, tobiasd]
Enhancements to the background workers [annando, miqrogroove, rabuzarus]
Enhancements to the PHP7.2 compatibility [annando, miqrogroove, MrPetovan]
Enhancements to the content filter [MrPetovan]
Enhancements to the hooks provided for addons [abanink]
Enhancements to the interaction with public postings [annando]
Enhancements to the config storage [frabrixxm]
Enhancements to the themes (frio, quattro, smoothly, vier) [annando, astifter, hoergen, MrPetovan, rabuzarus, tobiasd]
Enhancements to the handling of locks [nupplaphil]
Enhancements to the redis integration [nupplaphil]
Enhancements to the admin panel [JeroenED, tobiasd]
Enhancements to the user import process [annando]
Enhancements to the display of invitation information [JeroenED]
Enhancements to the automatic installation process [nupplaphil]
Enhancements to the contact group UI [annando, astifter]
Enhancements to the call of JS [hypolite]
Enhancements to the storage of items in the database [annando]
Enhancements to the process of changing relationships [annando]
Enhancements to the OEmbed of data [MrPetovan]
Fixed various PHP notice occurrences [annando, MrPetovan]
Fixed a bug that could lead to the display of posts from deleted accounts on the community page for a short period [annando]
Fixed a bug that prevented email notification to be send out [annando]
Fixed a bug in database optimisation [annando]
Fixed a bug during removing contacts [annando]
Fixed a bug in the tag-cloud widget [annando]
Fixed a bug in the daemon mode of the background worker [annando]
Fixed a bug in the frio theme that contact filtering [rabuzarus]
Fixed a bug that mangled the display of some additional smileys [abanink]
Fixed a bug in generating registration mails [MrPetovan]
Fixed a bug that caused blank re-share bodies [MrPetovon]
Fixed a bug in the API handling of private mails [fabrixxm]
Fixed a bug when calling the mail() function [miqrogroove]
Fixed a bug that caused deleted accounts being displayed in the local directory [miqrogroove]
Fixed a bug when checking the domain of an email address [VVelox]
Fixed a bug that prevented re-shares from Twitter to be shown as this [annando]
Fixed a bug that caused broken profile links [miqrogroove]
Fixed a bug that caused content from unknown accounts appearing in the timeline [annando]
Fixed a bug with the ignoring and blocking of contacts [annando]
Fixed a bug with showing hidden contacts in some places [annando]
Fixed a bug that prevented the deletion of events by contacts [annando]
Fixed a bug that prevented email contacts from being added [annando]
Fixed a bug in the notification/seen API call [fabrixxm]
Fixed a bug that prevented a refresh after un-/ignoring a conversation [annando]
Fixed a bug in the handling of some language translations [anndno]
Fixed a bug in the hook handling [annando]
Fixed the handling of too long tags [annando]
Fixed a bug that prevented the unliking of dis-/likes [annando]
Fixed bugs with the handling of private nodes [annando]
Fixed a bug in the session initialisation [annando]
Fixed bugs in the execution of the background processes [annando, Quix0r]
Fixed a problem with the notification page [MrPetovan]
Fixed a bug with wrong dates in importing some Atom feeds [annando]
Fixed forum exclusive distribution of postings using the !notation [annando]
Fixed a bug that lead to empty notifications [MrPetovan]
Fixed a problem that could sometimes prevent the execution of the relocation [annando]
Fixed a bug with the handling of images in postings over the connectors [annando]
Added conversation cleanup configuration [miqrogroove]
Added support of the usage of internal diaspora links to accounts [annando]
Added the possibility for admins to block certain nicknames (e.g. role names) [tobiasd]
Added the generation of system guid [nupplaphil]
Added the possibility for admins to mark a node for explicit content [tobiasd]
Added filter by account type to the community page [annando]
Added private flag to API results [fabrixxm]
Added post update checks to the console utility [annando]
Added codecov analysis [nupplaphil]
Added access-keys to the frio theme [tobiasd]
Added the profile settings to the user settings [tobiasd]
General code refactoring and beautification work [annando, MrPetovan, Quix0r, tobiasd]
Fixation of the position on the network page when new posts arrive [rabuzarus]
Ported OpenWebAuth from Hubzilla [annando, rabuzarus]
Removed hard coded syntax highlighting from code blocks [MrPetovan]
Removed (temporarily) the possibility to add pictures to private messages [annando]
New INI style config file format in /config [MrPetovan, tobiasd]
The .htaccess file is not part of the git repository anymore [annando, Quix0r]
Friendica Addons:
Update to the translations (CS, DE, EN-US, NL, PL, ZH-CN) [translation teams]
General update to adopt changes in core [annando, MrPetovan, Quix0r, tobiasd]
advancedcontentfilter:
Enhancement to the error handling [MrPetovan]
Honour the CSP settings [MrPetovan]
Fixed translation problems [annando]
blockem:
Enhancement of the settings [AlfredSK]
buffer:
support for app.net removed [annando]
js_upload:
Enhancement of the album name handling [rabuzarus]
Enhancement to the wording of the labels [astifter]
langfilter:
Fixed a problem with default values of the filtered languages [tobiasd]
libravatar:
The service wont shutdown, so we can keep the addon [tobiasd]
pumpio:
Fixed a problem that prevented new connections [annando]
superblock:
Fixed a bug that prevented the addon to block accounts [annando]
Enhancements of the settings [AlfredSK]
twitter:
Use rich text for quote tweets [MrPetovan]
Prevent empty quotes from being created [annando]
Fixed a problem with re-shares from remote_self contacts [annando]
Changed URL display after link expansion [MrPetovan]
Fixed a problem with EXIF handling [MrPetovan]
added addons:
mastodoncustomemojis [MrPetovan]
deprecated addons:
notimeline, retriver, remote_permissions, widgets
Directory:
Enhancements of the health summary [andyhee]
Enhancements of the PHP7 compatibility [MrPetovan]
Closed Issues:
901, 1034, 1074, 1303, 1308, 1391, 1490, 1470, 1559, 2093, 2337,
2340, 2381, 2396, 2675, 3291, 3299, 3493, 3501, 3535, 3643, 3840,
4148, 4419, 4475, 4507, 4655, 4659, 4710, 4726, 4739, 4753, 4814,
4830, 4868, 4889, 4923, 4971, 4950, 4985, 5066, 5099, 5137, 5148,
5158, 5168, 5188, 5202, 5211, 5222, 5233, 5243, 5247, 5252, 5257,
5260, 5262, 5268, 5274, 5275, 5276, 5278, 5298, 5318, 5319, 5320,
5321, 5322, 5330, 5333, 5341, 5365, 5405, 5407, 5411, 5423, 5432,
5434, 5436, 5443, 5455, 5464, 5467, 5469, 5486, 5496, 5497, 5514,
5539, 5524, 5541, 5544, 5550, 5564, 5566, 5605, 5630, 5638, 5651,
5653, 5660, 5670, 5691, 5733, 5745, 5768
Version 2018.05 (2018-06-01)
Friendica Core:
Update to the translations (DE, EN-GB, EN-US, FI, IS, IT, NL, PL, RU, ZN CH) [translation teams]

View file

@ -56,6 +56,13 @@ you wish to communicate with the Diaspora network.
- For alternative server configurations (such as Nginx server and MariaDB
database engine), refer to the wiki at https://github.com/friendica/friendica/wiki
This guide will walk you through the manual installation process of Friendica.
If this is nothing for you, you might be interested in
* the Friendica Docker image (https://github.com/friendica/docker) or
* how install Friendica with YunoHost (https://github.com/YunoHost-Apps/friendica_ynh).
2. Unpack the Friendica files into the root of your web server document area.
- If you copy the directory tree to your webserver, make sure

View file

@ -1 +1 @@
2018.08-rc
2018.12-dev

View file

@ -54,7 +54,7 @@ require_once "include/dba.php";
$a = new App(dirname(__DIR__));
if ($a->mode === App::MODE_NORMAL) {
if ($a->getMode()->isNormal()) {
$oAuth = new ExAuth();
$oAuth->readStdin();
}
}

Binary file not shown.

View file

@ -34,7 +34,7 @@ require_once "include/dba.php";
$a = new App(dirname(__DIR__));
if ($a->isInstallMode()) {
if ($a->getMode()->isInstall()) {
die("Friendica isn't properly installed yet.\n");
}

View file

@ -45,7 +45,7 @@ if (Config::get('system', 'maintenance', false, true)) {
return;
}
$a->set_baseurl(Config::get('system', 'url'));
$a->setBaseURL(Config::get('system', 'url'));
Addon::loadHooks();

164
boot.php
View file

@ -39,9 +39,9 @@ require_once 'include/text.php';
define('FRIENDICA_PLATFORM', 'Friendica');
define('FRIENDICA_CODENAME', 'The Tazmans Flax-lily');
define('FRIENDICA_VERSION', '2018.08-rc');
define('FRIENDICA_VERSION', '2018.12-dev');
define('DFRN_PROTOCOL_VERSION', '2.23');
define('DB_UPDATE_VERSION', 1283);
define('DB_UPDATE_VERSION', 1288);
define('NEW_UPDATE_ROUTINE_VERSION', 1170);
/**
@ -475,44 +475,6 @@ function defaults() {
return $return;
}
/**
* @brief Returns the baseurl.
*
* @see System::baseUrl()
*
* @return string
* @TODO Function is deprecated and only used in some addons
*/
function z_root()
{
return System::baseUrl();
}
/**
* @brief Return absolut URL for given $path.
*
* @param string $path given path
*
* @return string
*/
function absurl($path)
{
if (strpos($path, '/') === 0) {
return z_path() . $path;
}
return $path;
}
/**
* @brief Function to check if request was an AJAX (xmlhttprequest) request.
*
* @return boolean
*/
function is_ajax()
{
return (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest');
}
/**
* @brief Function to check if request was an AJAX (xmlhttprequest) request.
*
@ -556,7 +518,7 @@ function check_url(App $a)
// and www.example.com vs example.com.
// We will only change the url to an ip address if there is no existing setting
if (empty($url) || (!link_compare($url, System::baseUrl())) && (!preg_match("/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/", $a->get_hostname()))) {
if (empty($url) || (!link_compare($url, System::baseUrl())) && (!preg_match("/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/", $a->getHostName()))) {
Config::set('system', 'url', System::baseUrl());
}
@ -673,62 +635,6 @@ function run_update_function($x, $prefix)
}
}
/**
* @brief Synchronise addons:
*
* system.addon contains a comma-separated list of names
* of addons which are used on this system.
* Go through the database list of already installed addons, and if we have
* an entry, but it isn't in the config list, call the uninstall procedure
* and mark it uninstalled in the database (for now we'll remove it).
* Then go through the config list and if we have a addon that isn't installed,
* call the install procedure and add it to the database.
*
* @param object $a App
*/
function check_addons(App $a)
{
$r = q("SELECT * FROM `addon` WHERE `installed` = 1");
if (DBA::isResult($r)) {
$installed = $r;
} else {
$installed = [];
}
$addons = Config::get('system', 'addon');
$addons_arr = [];
if ($addons) {
$addons_arr = explode(',', str_replace(' ', '', $addons));
}
$a->addons = $addons_arr;
$installed_arr = [];
if (count($installed)) {
foreach ($installed as $i) {
if (!in_array($i['name'], $addons_arr)) {
Addon::uninstall($i['name']);
} else {
$installed_arr[] = $i['name'];
}
}
}
if (count($addons_arr)) {
foreach ($addons_arr as $p) {
if (!in_array($p, $installed_arr)) {
Addon::install($p);
}
}
}
Addon::loadHooks();
return;
}
/**
* @brief Used to end the current process, after saving session state.
* @deprecated
@ -741,7 +647,7 @@ function killme()
/**
* @brief Redirect to another URL and terminate this process.
*/
function goaway($path)
function goaway($path = '')
{
if (strstr(normalise_link($path), 'http://')) {
$url = $path;
@ -1031,27 +937,27 @@ function get_temppath()
$temppath = Config::get("system", "temppath");
if (($temppath != "") && App::directory_usable($temppath)) {
if (($temppath != "") && App::isDirectoryUsable($temppath)) {
// We have a temp path and it is usable
return App::realpath($temppath);
return App::getRealPath($temppath);
}
// We don't have a working preconfigured temp path, so we take the system path.
$temppath = sys_get_temp_dir();
// Check if it is usable
if (($temppath != "") && App::directory_usable($temppath)) {
if (($temppath != "") && App::isDirectoryUsable($temppath)) {
// Always store the real path, not the path through symlinks
$temppath = App::realpath($temppath);
$temppath = App::getRealPath($temppath);
// To avoid any interferences with other systems we create our own directory
$new_temppath = $temppath . "/" . $a->get_hostname();
$new_temppath = $temppath . "/" . $a->getHostName();
if (!is_dir($new_temppath)) {
/// @TODO There is a mkdir()+chmod() upwards, maybe generalize this (+ configurable) into a function/method?
mkdir($new_temppath);
}
if (App::directory_usable($new_temppath)) {
if (App::isDirectoryUsable($new_temppath)) {
// The new path is usable, we are happy
Config::set("system", "temppath", $new_temppath);
return $new_temppath;
@ -1133,8 +1039,8 @@ function get_itemcachepath()
}
$itemcache = Config::get('system', 'itemcache');
if (($itemcache != "") && App::directory_usable($itemcache)) {
return App::realpath($itemcache);
if (($itemcache != "") && App::isDirectoryUsable($itemcache)) {
return App::getRealPath($itemcache);
}
$temppath = get_temppath();
@ -1145,7 +1051,7 @@ function get_itemcachepath()
mkdir($itemcache);
}
if (App::directory_usable($itemcache)) {
if (App::isDirectoryUsable($itemcache)) {
Config::set("system", "itemcache", $itemcache);
return $itemcache;
}
@ -1161,7 +1067,7 @@ function get_itemcachepath()
function get_spoolpath()
{
$spoolpath = Config::get('system', 'spoolpath');
if (($spoolpath != "") && App::directory_usable($spoolpath)) {
if (($spoolpath != "") && App::isDirectoryUsable($spoolpath)) {
// We have a spool path and it is usable
return $spoolpath;
}
@ -1176,7 +1082,7 @@ function get_spoolpath()
mkdir($spoolpath);
}
if (App::directory_usable($spoolpath)) {
if (App::isDirectoryUsable($spoolpath)) {
// The new path is usable, we are happy
Config::set("system", "spoolpath", $spoolpath);
return $spoolpath;
@ -1231,46 +1137,6 @@ function validate_include(&$file)
return $valid;
}
function current_load()
{
if (!function_exists('sys_getloadavg')) {
return false;
}
$load_arr = sys_getloadavg();
if (!is_array($load_arr)) {
return false;
}
return max($load_arr[0], $load_arr[1]);
}
/**
* @brief get c-style args
*
* @return int
*/
function argc()
{
return get_app()->argc;
}
/**
* @brief Returns the value of a argv key
*
* @param int $x argv key
* @return string Value of the argv key
*/
function argv($x)
{
if (array_key_exists($x, get_app()->argv)) {
return get_app()->argv[$x];
}
return '';
}
/**
* @brief Get the data which is needed for infinite scroll
*

View file

@ -18,13 +18,13 @@
"asika/simple-console": "^1.0",
"divineomega/password_exposed": "^2.4",
"ezyang/htmlpurifier": "~4.7.0",
"league/html-to-markdown": "~4.4.1",
"friendica/json-ld": "^1.0",
"league/html-to-markdown": "~4.8.0",
"lightopenid/lightopenid": "dev-master",
"michelf/php-markdown": "^1.7",
"mobiledetect/mobiledetectlib": "2.8.*",
"paragonie/random_compat": "^2.0",
"pear/Text_LanguageDetect": "1.*",
"pear/Text_Highlighter": "dev-master",
"seld/cli-prompt": "^1.0",
"smarty/smarty": "^3.1",
"fxp/composer-asset-plugin": "~1.3",
@ -43,7 +43,7 @@
"repositories": [
{
"type": "vcs",
"url": "https://github.com/pear/Text_Highlighter"
"url": "https://git.friendi.ca/friendica/php-json-ld"
}
],
"autoload": {
@ -75,7 +75,8 @@
"phpunit/dbunit": "^2.0",
"phpdocumentor/reflection-docblock": "^3.0.2",
"phpunit/php-token-stream": "^1.4.2",
"mikey179/vfsStream": "^1.6"
"mikey179/vfsStream": "^1.6",
"mockery/mockery": "^1.2"
},
"scripts": {
"test": "phpunit"

979
composer.lock generated
View file

@ -1,10 +1,10 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "d62c3e3d6971ee63a862a22ff3cd3768",
"content-hash": "9f0dbeccbae197460a0ce74a940177cd",
"packages": [
{
"name": "asika/simple-console",
@ -41,16 +41,16 @@
},
{
"name": "bower-asset/Chart-js",
"version": "v2.7.1",
"version": "v2.7.2",
"source": {
"type": "git",
"url": "https://github.com/chartjs/Chart.js.git",
"reference": "0fead21939b92c15093c1b7d5ee2627fb5900fff"
"reference": "98f104cdd03617f1300b417b3d60c23d4e3e3403"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/chartjs/Chart.js/zipball/0fead21939b92c15093c1b7d5ee2627fb5900fff",
"reference": "0fead21939b92c15093c1b7d5ee2627fb5900fff",
"url": "https://api.github.com/repos/chartjs/Chart.js/zipball/98f104cdd03617f1300b417b3d60c23d4e3e3403",
"reference": "98f104cdd03617f1300b417b3d60c23d4e3e3403",
"shasum": ""
},
"type": "bower-asset-library",
@ -69,7 +69,7 @@
"MIT"
],
"description": "Simple HTML5 charts using the canvas element.",
"time": "2017-10-28T15:01:52+00:00"
"time": "2018-03-01T21:45:21+00:00"
},
{
"name": "bower-asset/base64",
@ -135,44 +135,133 @@
},
{
"name": "bower-asset/vue",
"version": "v2.5.16",
"version": "v2.5.17",
"source": {
"type": "git",
"url": "https://github.com/vuejs/vue.git",
"reference": "25342194016dc3bcc81cb3e8e229b0fb7ba1d1d6"
"reference": "636c9b4ef17f2062720b677cbbe613f146f4d4db"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/vuejs/vue/zipball/25342194016dc3bcc81cb3e8e229b0fb7ba1d1d6",
"reference": "25342194016dc3bcc81cb3e8e229b0fb7ba1d1d6",
"url": "https://api.github.com/repos/vuejs/vue/zipball/636c9b4ef17f2062720b677cbbe613f146f4d4db",
"reference": "636c9b4ef17f2062720b677cbbe613f146f4d4db",
"shasum": ""
},
"type": "bower-asset-library"
},
{
"name": "divineomega/password_exposed",
"version": "v2.5.1",
"name": "divineomega/do-file-cache",
"version": "v2.0.2",
"source": {
"type": "git",
"url": "https://github.com/DivineOmega/password_exposed.git",
"reference": "c928bf722eb02398df11076add60df070cb55581"
"url": "https://github.com/DivineOmega/DO-File-Cache.git",
"reference": "261c6e30a0de8cd325f826d08b2e51b2e367a1a3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/DivineOmega/password_exposed/zipball/c928bf722eb02398df11076add60df070cb55581",
"reference": "c928bf722eb02398df11076add60df070cb55581",
"url": "https://api.github.com/repos/DivineOmega/DO-File-Cache/zipball/261c6e30a0de8cd325f826d08b2e51b2e367a1a3",
"reference": "261c6e30a0de8cd325f826d08b2e51b2e367a1a3",
"shasum": ""
},
"require": {
"php": ">=5.6"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.1",
"phpunit/phpunit": "^6.5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-4": {
"DivineOmega\\DOFileCache\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0-only"
],
"description": "DO File Cache is a PHP File-based Caching Library. Its syntax is designed to closely resemble the PHP memcache extension.",
"keywords": [
"cache",
"caching",
"caching library",
"file cache",
"library",
"php"
],
"time": "2018-09-12T23:08:34+00:00"
},
{
"name": "divineomega/do-file-cache-psr-6",
"version": "v2.0.1",
"source": {
"type": "git",
"url": "https://github.com/DivineOmega/DO-File-Cache-PSR-6.git",
"reference": "18f9807d0491d093e9a12741afb40257d92f017e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/DivineOmega/DO-File-Cache-PSR-6/zipball/18f9807d0491d093e9a12741afb40257d92f017e",
"reference": "18f9807d0491d093e9a12741afb40257d92f017e",
"shasum": ""
},
"require": {
"divineomega/do-file-cache": "^2.0.0",
"psr/cache": "^1.0"
},
"require-dev": {
"cache/integration-tests": "^0.16.0",
"php-coveralls/php-coveralls": "^2.1",
"phpunit/phpunit": "^5.7"
},
"type": "library",
"autoload": {
"psr-4": {
"DivineOmega\\DOFileCachePSR6\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0-only"
],
"authors": [
{
"name": "Jordan Hall",
"email": "jordan@hall05.co.uk"
}
],
"description": "PSR-6 adapter for DO File Cache",
"time": "2018-07-13T08:32:36+00:00"
},
{
"name": "divineomega/password_exposed",
"version": "v2.5.3",
"source": {
"type": "git",
"url": "https://github.com/DivineOmega/password_exposed.git",
"reference": "1f1b49e3ec55b0f07115d342b145091368b081c4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/DivineOmega/password_exposed/zipball/1f1b49e3ec55b0f07115d342b145091368b081c4",
"reference": "1f1b49e3ec55b0f07115d342b145091368b081c4",
"shasum": ""
},
"require": {
"divineomega/do-file-cache-psr-6": "^2.0",
"guzzlehttp/guzzle": "^6.3",
"paragonie/certainty": "^1",
"php": ">=5.6",
"rapidwebltd/rw-file-cache-psr-6": "^1.0"
"php": ">=5.6"
},
"require-dev": {
"fzaninotto/faker": "^1.7",
"php-coveralls/php-coveralls": "^2.1",
"phpunit/phpunit": "^5.7",
"satooshi/php-coveralls": "^2.0",
"vimeo/psalm": "^1"
},
"type": "library",
@ -195,7 +284,7 @@
}
],
"description": "This PHP package provides a `password_exposed` helper function, that uses the haveibeenpwned.com API to check if a password has been exposed in a data breach.",
"time": "2018-04-02T18:16:36+00:00"
"time": "2018-07-12T22:09:43+00:00"
},
{
"name": "ezyang/htmlpurifier",
@ -241,18 +330,62 @@
],
"time": "2015-08-05T01:03:42+00:00"
},
{
"name": "friendica/json-ld",
"version": "1.1.1",
"source": {
"type": "git",
"url": "https://git.friendi.ca/friendica/php-json-ld",
"reference": "ca3916d10d2ad9073b3b1eae383978dbe828e1e1"
},
"require": {
"ext-json": "*",
"php": ">=5.4.0"
},
"type": "library",
"autoload": {
"files": [
"jsonld.php"
]
},
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Digital Bazaar, Inc.",
"email": "support@digitalbazaar.com",
"homepage": "http://digitalbazaar.com/"
},
{
"name": "Friendica Team",
"homepage": "https://friendi.ca/"
}
],
"description": "A JSON-LD Processor and API implementation in PHP.",
"homepage": "https://git.friendi.ca/friendica/php-json-ld",
"keywords": [
"JSON",
"JSON-LD",
"Linked Data",
"RDF",
"Semantic Web",
"jsonld"
],
"time": "2018-10-08T20:41:00+00:00"
},
{
"name": "fxp/composer-asset-plugin",
"version": "v1.4.2",
"version": "v1.4.4",
"source": {
"type": "git",
"url": "https://github.com/fxpio/composer-asset-plugin.git",
"reference": "61352d99940d2b2392a5d2db83b8c0ef5faf222a"
"reference": "0d07328eef6e6f3753aa835fd2faef7fed1717bf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fxpio/composer-asset-plugin/zipball/61352d99940d2b2392a5d2db83b8c0ef5faf222a",
"reference": "61352d99940d2b2392a5d2db83b8c0ef5faf222a",
"url": "https://api.github.com/repos/fxpio/composer-asset-plugin/zipball/0d07328eef6e6f3753aa835fd2faef7fed1717bf",
"reference": "0d07328eef6e6f3753aa835fd2faef7fed1717bf",
"shasum": ""
},
"require": {
@ -260,7 +393,7 @@
"php": ">=5.3.3"
},
"require-dev": {
"composer/composer": "^1.4.0"
"composer/composer": "^1.6.0"
},
"type": "composer-plugin",
"extra": {
@ -298,20 +431,20 @@
"npm",
"package"
],
"time": "2017-10-20T06:53:56+00:00"
"time": "2018-07-02T11:37:17+00:00"
},
{
"name": "guzzlehttp/guzzle",
"version": "6.3.0",
"version": "6.3.3",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699"
"reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/f4db5a78a5ea468d4831de7f0bf9d9415e348699",
"reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba",
"reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba",
"shasum": ""
},
"require": {
@ -321,7 +454,7 @@
},
"require-dev": {
"ext-curl": "*",
"phpunit/phpunit": "^4.0 || ^5.0",
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
"psr/log": "^1.0"
},
"suggest": {
@ -330,7 +463,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "6.2-dev"
"dev-master": "6.3-dev"
}
},
"autoload": {
@ -363,7 +496,7 @@
"rest",
"web service"
],
"time": "2017-06-22T18:50:49+00:00"
"time": "2018-04-22T15:46:56+00:00"
},
{
"name": "guzzlehttp/promises",
@ -483,16 +616,16 @@
},
{
"name": "league/html-to-markdown",
"version": "4.4.1",
"version": "4.8.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/html-to-markdown.git",
"reference": "82ea375b5b2b1da1da222644c0565c695bf88186"
"reference": "f9a879a068c68ff47b722de63f58bec79e448f9d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/82ea375b5b2b1da1da222644c0565c695bf88186",
"reference": "82ea375b5b2b1da1da222644c0565c695bf88186",
"url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/f9a879a068c68ff47b722de63f58bec79e448f9d",
"reference": "f9a879a068c68ff47b722de63f58bec79e448f9d",
"shasum": ""
},
"require": {
@ -511,7 +644,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.5-dev"
"dev-master": "4.9-dev"
}
},
"autoload": {
@ -524,17 +657,17 @@
"MIT"
],
"authors": [
{
"name": "Colin O'Dell",
"email": "colinodell@gmail.com",
"homepage": "http://www.colinodell.com",
"role": "Lead Developer"
},
{
"name": "Nick Cernis",
"email": "nick@cern.is",
"homepage": "http://modernnerd.net",
"role": "Original Author"
},
{
"name": "Colin O'Dell",
"email": "colinodell@gmail.com",
"homepage": "https://www.colinodell.com",
"role": "Lead Developer"
}
],
"description": "An HTML-to-markdown conversion helper for PHP",
@ -543,7 +676,7 @@
"html",
"markdown"
],
"time": "2017-03-16T00:45:59+00:00"
"time": "2018-09-18T12:18:08+00:00"
},
{
"name": "lightopenid/lightopenid",
@ -626,16 +759,16 @@
},
{
"name": "mobiledetect/mobiledetectlib",
"version": "2.8.30",
"version": "2.8.33",
"source": {
"type": "git",
"url": "https://github.com/serbanghita/Mobile-Detect.git",
"reference": "5500bbbf312fe77ef0c7223858dad84fe49ee0c3"
"reference": "cd385290f9a0d609d2eddd165a1e44ec1bf12102"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/serbanghita/Mobile-Detect/zipball/5500bbbf312fe77ef0c7223858dad84fe49ee0c3",
"reference": "5500bbbf312fe77ef0c7223858dad84fe49ee0c3",
"url": "https://api.github.com/repos/serbanghita/Mobile-Detect/zipball/cd385290f9a0d609d2eddd165a1e44ec1bf12102",
"reference": "cd385290f9a0d609d2eddd165a1e44ec1bf12102",
"shasum": ""
},
"require": {
@ -674,7 +807,7 @@
"mobile detector",
"php mobile detect"
],
"time": "2017-12-18T10:38:51+00:00"
"time": "2018-09-01T15:05:15+00:00"
},
{
"name": "npm-asset/cropperjs",
@ -815,67 +948,16 @@
},
{
"name": "npm-asset/fullcalendar",
"version": "3.8.2",
"version": "3.9.0",
"dist": {
"type": "tar",
"url": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-3.8.2.tgz",
"url": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-3.9.0.tgz",
"reference": null,
"shasum": "ef7dc77b89134bbe6163e51136f7a1f8bfc1d807"
"shasum": "b608a9989f3416f0b1d526c6bdfeeaf2ac79eda5"
},
"require": {
"npm-asset/jquery": ">=2,<4.0",
"npm-asset/moment": ">=2.9.0,<3.0.0"
},
"require-dev": {
"npm-asset/awesome-typescript-loader": ">=3.3.0,<4.0.0",
"npm-asset/bootstrap": ">=3.3.7,<4.0.0",
"npm-asset/components-jqueryui": "dev-github:components/jqueryui",
"npm-asset/css-loader": ">=0.28.7,<0.29.0",
"npm-asset/del": ">=2.2.1,<3.0.0",
"npm-asset/dts-generator": ">=2.1.0,<3.0.0",
"npm-asset/eslint": ">=4.13.1,<5.0.0",
"npm-asset/eslint-config-standard": ">=11.0.0-beta.0,<12.0.0",
"npm-asset/eslint-plugin-import": ">=2.8.0,<3.0.0",
"npm-asset/eslint-plugin-node": ">=5.2.1,<6.0.0",
"npm-asset/eslint-plugin-promise": ">=3.6.0,<4.0.0",
"npm-asset/eslint-plugin-standard": ">=3.0.1,<4.0.0",
"npm-asset/extract-text-webpack-plugin": ">=3.0.2,<4.0.0",
"npm-asset/glob": ">=7.1.2,<8.0.0",
"npm-asset/gulp": ">=3.9.1,<4.0.0",
"npm-asset/gulp-cssmin": ">=0.1.7,<0.2.0",
"npm-asset/gulp-eslint": ">=4.0.0,<5.0.0",
"npm-asset/gulp-filter": ">=4.0.0,<5.0.0",
"npm-asset/gulp-modify-file": ">=1.0.0,<2.0.0",
"npm-asset/gulp-rename": ">=1.2.2,<2.0.0",
"npm-asset/gulp-shell": ">=0.6.5,<0.7.0",
"npm-asset/gulp-tslint": ">=8.1.2,<9.0.0",
"npm-asset/gulp-uglify": ">=2.0.0,<3.0.0",
"npm-asset/gulp-util": ">=3.0.7,<4.0.0",
"npm-asset/gulp-watch": ">=4.3.11,<5.0.0",
"npm-asset/gulp-zip": ">=3.2.0,<4.0.0",
"npm-asset/jasmine-core": "2.5.2",
"npm-asset/jasmine-fixture": ">=2.0.0,<3.0.0",
"npm-asset/jasmine-jquery": ">=2.1.1,<3.0.0",
"npm-asset/jquery-mockjax": ">=2.2.0,<3.0.0",
"npm-asset/jquery-simulate": "dev-github:jquery/jquery-simulate",
"npm-asset/karma": ">=0.13.22,<0.14.0",
"npm-asset/karma-jasmine": ">=1.0.2,<2.0.0",
"npm-asset/karma-phantomjs-launcher": ">=1.0.0,<2.0.0",
"npm-asset/karma-sourcemap-loader": ">=0.3.7,<0.4.0",
"npm-asset/karma-verbose-reporter": "0.0.6",
"npm-asset/moment-timezone": ">=0.5.5,<0.6.0",
"npm-asset/native-promise-only": ">=0.8.1,<0.9.0",
"npm-asset/node-sass": ">=4.7.2,<5.0.0",
"npm-asset/phantomjs-prebuilt": ">=2.1.7,<3.0.0",
"npm-asset/sass-loader": ">=6.0.6,<7.0.0",
"npm-asset/tslib": ">=1.8.0,<2.0.0",
"npm-asset/tslint": ">=5.8.0,<6.0.0",
"npm-asset/tslint-config-standard": ">=7.0.0,<8.0.0",
"npm-asset/types--jquery": "2.0.47",
"npm-asset/typescript": ">=2.6.2,<3.0.0",
"npm-asset/webpack": ">=3.8.1,<4.0.0",
"npm-asset/webpack-stream": ">=4.0.0,<5.0.0",
"npm-asset/yargs": ">=4.8.1,<5.0.0"
"npm-asset/moment": ">=2.20.1,<3.0.0"
},
"type": "npm-asset-library",
"extra": {
@ -923,7 +1005,7 @@
"full-sized",
"jquery-plugin"
],
"time": "2018-01-30T23:49:01+00:00"
"time": "2018-03-05T03:30:23+00:00"
},
{
"name": "npm-asset/imagesloaded",
@ -1150,24 +1232,18 @@
},
{
"name": "npm-asset/jquery-datetimepicker",
"version": "2.5.17",
"version": "2.5.20",
"dist": {
"type": "tar",
"url": "https://registry.npmjs.org/jquery-datetimepicker/-/jquery-datetimepicker-2.5.17.tgz",
"url": "https://registry.npmjs.org/jquery-datetimepicker/-/jquery-datetimepicker-2.5.20.tgz",
"reference": null,
"shasum": "8857a631f248081d4072563bde40fa8c17e407b1"
"shasum": "687d6204b90b03dc93f725f8df036e1d061f37ac"
},
"require": {
"npm-asset/jquery": ">=1.7.2",
"npm-asset/jquery-mousewheel": ">=3.1.13",
"npm-asset/php-date-formatter": ">=1.3.4,<2.0.0"
},
"require-dev": {
"npm-asset/concat": "dev-github:azer/concat",
"npm-asset/concat-cli": ">=4.0.0,<5.0.0",
"npm-asset/uglifycss": ">=0.0.27,<0.0.28",
"npm-asset/uglifyjs": ">=2.4.10,<3.0.0"
},
"type": "npm-asset-library",
"extra": {
"npm-asset-bugs": {
@ -1180,13 +1256,13 @@
"url": "git+https://github.com/xdan/datetimepicker.git"
},
"npm-asset-scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"test": "karma start --browsers Firefox karma.conf.js --single-run",
"concat": "concat-cli -f node_modules/php-date-formatter/js/php-date-formatter.min.js jquery.datetimepicker.js node_modules/jquery-mousewheel/jquery.mousewheel.js -o build/jquery.datetimepicker.full.js",
"minify": "uglifyjs jquery.datetimepicker.js -c -m -o build/jquery.datetimepicker.min.js && uglifycss jquery.datetimepicker.css > build/jquery.datetimepicker.min.css",
"minifyconcat": "uglifyjs build/jquery.datetimepicker.full.js -c -m -o build/jquery.datetimepicker.full.min.js",
"github": "git add --all && git commit -m \"New version %npm_package_version% \" && git tag %npm_package_version% && git push --tags origin HEAD:master && npm publish",
"build": "npm run minify && npm run concat && npm run minifyconcat",
"public": "npm version patch --no-git-tag-version && npm run build && npm run github"
"public": "npm run test && npm version patch --no-git-tag-version && npm run build && npm run github"
}
},
"license": [
@ -1196,7 +1272,7 @@
{
"name": "Chupurnov",
"email": "chupurnov@gmail.com",
"url": "http://xdsoft.net/"
"url": "https://xdsoft.net/"
}
],
"description": "jQuery Plugin DateTimePicker it is DatePicker and TimePicker in one",
@ -1210,7 +1286,7 @@
"time",
"timepicker"
],
"time": "2018-01-23T05:56:50+00:00"
"time": "2018-03-21T16:26:39+00:00"
},
{
"name": "npm-asset/jquery-mousewheel",
@ -1269,45 +1345,12 @@
},
{
"name": "npm-asset/moment",
"version": "2.20.1",
"version": "2.22.2",
"dist": {
"type": "tar",
"url": "https://registry.npmjs.org/moment/-/moment-2.20.1.tgz",
"url": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz",
"reference": null,
"shasum": "d6eb1a46cbcc14a2b2f9434112c1ff8907f313fd"
},
"require-dev": {
"npm-asset/benchmark": "dev-default|*",
"npm-asset/coveralls": ">=2.11.2,<3.0.0",
"npm-asset/es6-promise": "dev-default|*",
"npm-asset/grunt": "~0.4",
"npm-asset/grunt-benchmark": "dev-default|*",
"npm-asset/grunt-cli": "dev-default|*",
"npm-asset/grunt-contrib-clean": "dev-default|*",
"npm-asset/grunt-contrib-concat": "dev-default|*",
"npm-asset/grunt-contrib-copy": "dev-default|*",
"npm-asset/grunt-contrib-jshint": "dev-default|*",
"npm-asset/grunt-contrib-uglify": "dev-default|*",
"npm-asset/grunt-contrib-watch": "dev-default|*",
"npm-asset/grunt-env": "dev-default|*",
"npm-asset/grunt-exec": "dev-default|*",
"npm-asset/grunt-jscs": "dev-default|*",
"npm-asset/grunt-karma": "dev-default|*",
"npm-asset/grunt-nuget": "dev-default|*",
"npm-asset/grunt-string-replace": "dev-default|*",
"npm-asset/karma": "dev-default|*",
"npm-asset/karma-chrome-launcher": "dev-default|*",
"npm-asset/karma-firefox-launcher": "dev-default|*",
"npm-asset/karma-qunit": "dev-default|*",
"npm-asset/karma-sauce-launcher": "dev-default|*",
"npm-asset/load-grunt-tasks": "dev-default|*",
"npm-asset/nyc": ">=2.1.4,<3.0.0",
"npm-asset/qunit": ">=0.7.5,<0.8.0",
"npm-asset/qunit-cli": ">=0.1.4,<0.2.0",
"npm-asset/rollup": "dev-default|*",
"npm-asset/spacejam": "dev-default|*",
"npm-asset/typescript": ">=1.8.10,<2.0.0",
"npm-asset/uglify-js": "dev-default|*"
"shasum": "3c257f9839fc0e93ff53149632239eb90783ff66"
},
"type": "npm-asset-library",
"extra": {
@ -1377,16 +1420,21 @@
"time",
"validate"
],
"time": "2017-12-19T04:44:18+00:00"
"time": "2018-06-01T06:58:41+00:00"
},
{
"name": "npm-asset/php-date-formatter",
"version": "1.3.4",
"version": "v1.3.5",
"source": {
"type": "git",
"url": "https://github.com/kartik-v/php-date-formatter.git",
"reference": "d842e1c4e6a8d6108017b726321c305bb5ae4fb5"
},
"dist": {
"type": "tar",
"url": "https://registry.npmjs.org/php-date-formatter/-/php-date-formatter-1.3.4.tgz",
"reference": null,
"shasum": "09a15ae0766ba0beb1900c27c1ec319ef2e4563e"
"type": "zip",
"url": "https://api.github.com/repos/kartik-v/php-date-formatter/zipball/d842e1c4e6a8d6108017b726321c305bb5ae4fb5",
"reference": "d842e1c4e6a8d6108017b726321c305bb5ae4fb5",
"shasum": ""
},
"type": "npm-asset-library",
"extra": {
@ -1399,35 +1447,31 @@
},
"npm-asset-repository": {
"type": "git",
"url": "git+https://github.com/kartik-v/php-date-formatter.git"
},
"npm-asset-scripts": []
"url": "https://github.com/kartik-v/php-date-formatter.git"
}
},
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Kartik Visweswaran",
"email": "kartikv2@gmail.com"
}
"Kartik Visweswaran <kartikv2@gmail.com>"
],
"description": "A Javascript datetime formatting and manipulation library using PHP date-time formats.",
"homepage": "https://github.com/kartik-v/php-date-formatter",
"time": "2016-02-18T15:15:55+00:00"
"time": "2018-07-13T06:56:46+00:00"
},
{
"name": "paragonie/certainty",
"version": "v1.0.2",
"version": "v1.0.4",
"source": {
"type": "git",
"url": "https://github.com/paragonie/certainty.git",
"reference": "a2d14f5b0b85c58329dee248d77d34e7e1202a32"
"reference": "d0f22c0fe579cf0e4f8ee301de5bc97ab124faac"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/certainty/zipball/a2d14f5b0b85c58329dee248d77d34e7e1202a32",
"reference": "a2d14f5b0b85c58329dee248d77d34e7e1202a32",
"url": "https://api.github.com/repos/paragonie/certainty/zipball/d0f22c0fe579cf0e4f8ee301de5bc97ab124faac",
"reference": "d0f22c0fe579cf0e4f8ee301de5bc97ab124faac",
"shasum": ""
},
"require": {
@ -1474,29 +1518,29 @@
"ssl",
"tls"
],
"time": "2018-03-12T18:34:23+00:00"
"time": "2018-04-09T07:21:55+00:00"
},
{
"name": "paragonie/constant_time_encoding",
"version": "v1.0.2",
"version": "v1.0.4",
"source": {
"type": "git",
"url": "https://github.com/paragonie/constant_time_encoding.git",
"reference": "6111a38faf6fdebc14e36652d22036f379ba58d3"
"reference": "2132f0f293d856026d7d11bd81b9f4a23a1dc1f6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/6111a38faf6fdebc14e36652d22036f379ba58d3",
"reference": "6111a38faf6fdebc14e36652d22036f379ba58d3",
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/2132f0f293d856026d7d11bd81b9f4a23a1dc1f6",
"reference": "2132f0f293d856026d7d11bd81b9f4a23a1dc1f6",
"shasum": ""
},
"require": {
"php": "^5.3|^7"
},
"require-dev": {
"paragonie/random_compat": "^1|^2",
"paragonie/random_compat": "^1.4|^2",
"phpunit/phpunit": "4.*|5.*",
"vimeo/psalm": "^1"
"vimeo/psalm": "^0.3|^1"
},
"type": "library",
"autoload": {
@ -1537,20 +1581,20 @@
"hex2bin",
"rfc4648"
],
"time": "2018-03-10T19:46:06+00:00"
"time": "2018-04-30T17:57:16+00:00"
},
{
"name": "paragonie/random_compat",
"version": "v2.0.11",
"version": "v2.0.17",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
"reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8"
"reference": "29af24f25bab834fcbb38ad2a69fa93b867e070d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/5da4d3c796c275c55f057af5a643ae297d96b4d8",
"reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/29af24f25bab834fcbb38ad2a69fa93b867e070d",
"reference": "29af24f25bab834fcbb38ad2a69fa93b867e070d",
"shasum": ""
},
"require": {
@ -1582,27 +1626,28 @@
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
"keywords": [
"csprng",
"polyfill",
"pseudorandom",
"random"
],
"time": "2017-09-27T21:40:39+00:00"
"time": "2018-07-04T16:31:37+00:00"
},
{
"name": "paragonie/sodium_compat",
"version": "v1.6.0",
"version": "v1.7.0",
"source": {
"type": "git",
"url": "https://github.com/paragonie/sodium_compat.git",
"reference": "1f6e5682eff4a5a6a394b14331a1904f1740e432"
"reference": "7b73005be3c224f12c47bd75a23ce24b762e47e8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/1f6e5682eff4a5a6a394b14331a1904f1740e432",
"reference": "1f6e5682eff4a5a6a394b14331a1904f1740e432",
"url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/7b73005be3c224f12c47bd75a23ce24b762e47e8",
"reference": "7b73005be3c224f12c47bd75a23ce24b762e47e8",
"shasum": ""
},
"require": {
"paragonie/random_compat": "^1|^2",
"paragonie/random_compat": ">=1",
"php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7"
},
"require-dev": {
@ -1667,205 +1712,7 @@
"secret-key cryptography",
"side-channel resistant"
],
"time": "2018-02-15T05:50:20+00:00"
},
{
"name": "pear/console_getopt",
"version": "v1.4.1",
"source": {
"type": "git",
"url": "https://github.com/pear/Console_Getopt.git",
"reference": "82f05cd1aa3edf34e19aa7c8ca312ce13a6a577f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pear/Console_Getopt/zipball/82f05cd1aa3edf34e19aa7c8ca312ce13a6a577f",
"reference": "82f05cd1aa3edf34e19aa7c8ca312ce13a6a577f",
"shasum": ""
},
"type": "library",
"autoload": {
"psr-0": {
"Console": "./"
}
},
"notification-url": "https://packagist.org/downloads/",
"include-path": [
"./"
],
"license": [
"BSD-2-Clause"
],
"authors": [
{
"name": "Greg Beaver",
"email": "cellog@php.net",
"role": "Helper"
},
{
"name": "Andrei Zmievski",
"email": "andrei@php.net",
"role": "Lead"
},
{
"name": "Stig Bakken",
"email": "stig@php.net",
"role": "Developer"
}
],
"description": "More info available on: http://pear.php.net/package/Console_Getopt",
"time": "2015-07-20T20:28:12+00:00"
},
{
"name": "pear/pear-core-minimal",
"version": "v1.10.3",
"source": {
"type": "git",
"url": "https://github.com/pear/pear-core-minimal.git",
"reference": "070f0b600b2caca2501e2c9b7e553016e4b0d115"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pear/pear-core-minimal/zipball/070f0b600b2caca2501e2c9b7e553016e4b0d115",
"reference": "070f0b600b2caca2501e2c9b7e553016e4b0d115",
"shasum": ""
},
"require": {
"pear/console_getopt": "~1.4",
"pear/pear_exception": "~1.0"
},
"replace": {
"rsky/pear-core-min": "self.version"
},
"type": "library",
"autoload": {
"psr-0": {
"": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"include-path": [
"src/"
],
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Christian Weiske",
"email": "cweiske@php.net",
"role": "Lead"
}
],
"description": "Minimal set of PEAR core files to be used as composer dependency",
"time": "2017-02-28T16:46:11+00:00"
},
{
"name": "pear/pear_exception",
"version": "v1.0.0",
"source": {
"type": "git",
"url": "https://github.com/pear/PEAR_Exception.git",
"reference": "8c18719fdae000b690e3912be401c76e406dd13b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pear/PEAR_Exception/zipball/8c18719fdae000b690e3912be401c76e406dd13b",
"reference": "8c18719fdae000b690e3912be401c76e406dd13b",
"shasum": ""
},
"require": {
"php": ">=4.4.0"
},
"require-dev": {
"phpunit/phpunit": "*"
},
"type": "class",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-0": {
"PEAR": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"include-path": [
"."
],
"license": [
"BSD-2-Clause"
],
"authors": [
{
"name": "Helgi Thormar",
"email": "dufuz@php.net"
},
{
"name": "Greg Beaver",
"email": "cellog@php.net"
}
],
"description": "The PEAR Exception base class.",
"homepage": "https://github.com/pear/PEAR_Exception",
"keywords": [
"exception"
],
"time": "2015-02-10T20:07:52+00:00"
},
{
"name": "pear/text_highlighter",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/pear/Text_Highlighter.git",
"reference": "2ccac2d9eaf55dc08bbbdb7136c93fb399d0f855"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pear/Text_Highlighter/zipball/2ccac2d9eaf55dc08bbbdb7136c93fb399d0f855",
"reference": "2ccac2d9eaf55dc08bbbdb7136c93fb399d0f855",
"shasum": ""
},
"require": {
"pear/pear-core-minimal": "~1.10.0",
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "@stable"
},
"type": "library",
"autoload": {
"psr-0": {
"Text": "./"
}
},
"include-path": [
"./"
],
"license": [
"PHP-3.01"
],
"authors": [
{
"email": "ssttoo@gmail.com",
"name": "Stoyan Stefanov",
"role": "Lead"
},
{
"email": "demenev@gmail.com",
"name": "Andrey Demenev",
"role": "Lead"
}
],
"description": "More info available on: http://pear.php.net/package/Text_Highlighter",
"support": {
"issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Text_Highlighter",
"source": "https://github.com/pear/Text_Highlighter"
},
"time": "2018-01-27T08:24:15+00:00"
"time": "2018-09-22T03:59:58+00:00"
},
{
"name": "pear/text_languagedetect",
@ -2007,94 +1854,6 @@
],
"time": "2016-08-06T14:39:51+00:00"
},
{
"name": "rapidwebltd/rw-file-cache",
"version": "v1.2.5",
"source": {
"type": "git",
"url": "https://github.com/rapidwebltd/RW-File-Cache.git",
"reference": "4a1d5aaefa6ffafec8e2d60787f12bcd9890977e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/rapidwebltd/RW-File-Cache/zipball/4a1d5aaefa6ffafec8e2d60787f12bcd9890977e",
"reference": "4a1d5aaefa6ffafec8e2d60787f12bcd9890977e",
"shasum": ""
},
"require": {
"php": ">=5.2.1"
},
"require-dev": {
"phpunit/phpunit": "^5.7"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-4": {
"rapidweb\\RWFileCache\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0-only"
],
"description": "RW File Cache is a PHP File-based Caching Library. Its syntax is designed to closely resemble the PHP memcache extension.",
"homepage": "https://github.com/rapidwebltd/RW-File-Cache",
"keywords": [
"cache",
"caching",
"caching library",
"file cache",
"library",
"php"
],
"time": "2018-01-23T17:20:58+00:00"
},
{
"name": "rapidwebltd/rw-file-cache-psr-6",
"version": "v1.0.0",
"source": {
"type": "git",
"url": "https://github.com/rapidwebltd/RW-File-Cache-PSR-6.git",
"reference": "b74ea201d4c964f0e6db0fb036d1ab28a570df66"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/rapidwebltd/RW-File-Cache-PSR-6/zipball/b74ea201d4c964f0e6db0fb036d1ab28a570df66",
"reference": "b74ea201d4c964f0e6db0fb036d1ab28a570df66",
"shasum": ""
},
"require": {
"psr/cache": "^1.0",
"rapidwebltd/rw-file-cache": "^1.2.3"
},
"require-dev": {
"cache/integration-tests": "^0.16.0",
"phpunit/phpunit": "^5.7"
},
"type": "library",
"autoload": {
"psr-4": {
"rapidweb\\RWFileCachePSR6\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0-only"
],
"authors": [
{
"name": "Jordan Hall",
"email": "jordan.hall@rapidweb.biz"
}
],
"description": "PSR-6 adapter for RW File Cache",
"time": "2018-01-30T19:13:45+00:00"
},
{
"name": "seld/cli-prompt",
"version": "1.0.3",
@ -2145,16 +1904,16 @@
},
{
"name": "smarty/smarty",
"version": "v3.1.31",
"version": "v3.1.33",
"source": {
"type": "git",
"url": "https://github.com/smarty-php/smarty.git",
"reference": "c7d42e4a327c402897dd587871434888fde1e7a9"
"reference": "dd55b23121e55a3b4f1af90a707a6c4e5969530f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/smarty-php/smarty/zipball/c7d42e4a327c402897dd587871434888fde1e7a9",
"reference": "c7d42e4a327c402897dd587871434888fde1e7a9",
"url": "https://api.github.com/repos/smarty-php/smarty/zipball/dd55b23121e55a3b4f1af90a707a6c4e5969530f",
"reference": "dd55b23121e55a3b4f1af90a707a6c4e5969530f",
"shasum": ""
},
"require": {
@ -2194,7 +1953,7 @@
"keywords": [
"templating"
],
"time": "2016-12-14T21:57:25+00:00"
"time": "2018-09-12T20:54:16+00:00"
}
],
"packages-dev": [
@ -2252,6 +2011,54 @@
],
"time": "2015-06-14T21:17:01+00:00"
},
{
"name": "hamcrest/hamcrest-php",
"version": "v2.0.0",
"source": {
"type": "git",
"url": "https://github.com/hamcrest/hamcrest-php.git",
"reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/776503d3a8e85d4f9a1148614f95b7a608b046ad",
"reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad",
"shasum": ""
},
"require": {
"php": "^5.3|^7.0"
},
"replace": {
"cordoval/hamcrest-php": "*",
"davedevelopment/hamcrest-php": "*",
"kodova/hamcrest-php": "*"
},
"require-dev": {
"phpunit/php-file-iterator": "1.3.3",
"phpunit/phpunit": "~4.0",
"satooshi/php-coveralls": "^1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
}
},
"autoload": {
"classmap": [
"hamcrest"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD"
],
"description": "This is the PHP port of Hamcrest Matchers",
"keywords": [
"test"
],
"time": "2016-01-20T08:20:44+00:00"
},
{
"name": "mikey179/vfsStream",
"version": "v1.6.5",
@ -2298,6 +2105,71 @@
"homepage": "http://vfs.bovigo.org/",
"time": "2017-08-01T08:02:14+00:00"
},
{
"name": "mockery/mockery",
"version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/mockery/mockery.git",
"reference": "100633629bf76d57430b86b7098cd6beb996a35a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mockery/mockery/zipball/100633629bf76d57430b86b7098cd6beb996a35a",
"reference": "100633629bf76d57430b86b7098cd6beb996a35a",
"shasum": ""
},
"require": {
"hamcrest/hamcrest-php": "~2.0",
"lib-pcre": ">=7.0",
"php": ">=5.6.0"
},
"require-dev": {
"phpunit/phpunit": "~5.7.10|~6.5|~7.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-0": {
"Mockery": "library/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Pádraic Brady",
"email": "padraic.brady@gmail.com",
"homepage": "http://blog.astrumfutura.com"
},
{
"name": "Dave Marshall",
"email": "dave.marshall@atstsolutions.co.uk",
"homepage": "http://davedevelopment.co.uk"
}
],
"description": "Mockery is a simple yet flexible PHP mock object framework",
"homepage": "https://github.com/mockery/mockery",
"keywords": [
"BDD",
"TDD",
"library",
"mock",
"mock objects",
"mockery",
"stub",
"test",
"test double",
"testing"
],
"time": "2018-10-02T21:52:37+00:00"
},
{
"name": "myclabs/deep-copy",
"version": "1.7.0",
@ -2343,53 +2215,6 @@
],
"time": "2017-10-19T19:58:43+00:00"
},
{
"name": "phar-io/version",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/phar-io/version.git",
"reference": "a70c0ced4be299a63d32fa96d9281d03e94041df"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df",
"reference": "a70c0ced4be299a63d32fa96d9281d03e94041df",
"shasum": ""
},
"require": {
"php": "^5.6 || ^7.0"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Arne Blankerts",
"email": "arne@blankerts.de",
"role": "Developer"
},
{
"name": "Sebastian Heuer",
"email": "sebastian@phpeople.de",
"role": "Developer"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "Developer"
}
],
"description": "Library for handling version information and constraints",
"time": "2017-03-05T17:38:23+00:00"
},
{
"name": "phpdocumentor/reflection-common",
"version": "1.0.1",
@ -2538,16 +2363,16 @@
},
{
"name": "phpspec/prophecy",
"version": "1.7.6",
"version": "1.8.0",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
"reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712"
"reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/33a7e3c4fda54e912ff6338c48823bd5c0f0b712",
"reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06",
"reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06",
"shasum": ""
},
"require": {
@ -2559,12 +2384,12 @@
},
"require-dev": {
"phpspec/phpspec": "^2.5|^3.2",
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5"
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.7.x-dev"
"dev-master": "1.8.x-dev"
}
},
"autoload": {
@ -2597,7 +2422,7 @@
"spy",
"stub"
],
"time": "2018-04-18T13:57:24+00:00"
"time": "2018-08-05T17:53:17+00:00"
},
{
"name": "phpunit/dbunit",
@ -3558,21 +3383,80 @@
"time": "2016-10-03T07:35:21+00:00"
},
{
"name": "symfony/yaml",
"version": "v3.4.8",
"name": "symfony/polyfill-ctype",
"version": "v1.9.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "a42f9da85c7c38d59f5e53f076fe81a091f894d0"
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "e3d826245268269cd66f8326bd8bc066687b4a19"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/a42f9da85c7c38d59f5e53f076fe81a091f894d0",
"reference": "a42f9da85c7c38d59f5e53f076fe81a091f894d0",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19",
"reference": "e3d826245268269cd66f8326bd8bc066687b4a19",
"shasum": ""
},
"require": {
"php": "^5.5.9|>=7.0.8"
"php": ">=5.3.3"
},
"suggest": {
"ext-ctype": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.9-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
},
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"time": "2018-08-06T14:22:27+00:00"
},
{
"name": "symfony/yaml",
"version": "v3.4.16",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "61973ecda60e9f3561e929e19c07d4878b960fc1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/61973ecda60e9f3561e929e19c07d4878b960fc1",
"reference": "61973ecda60e9f3561e929e19c07d4878b960fc1",
"shasum": ""
},
"require": {
"php": "^5.5.9|>=7.0.8",
"symfony/polyfill-ctype": "~1.8"
},
"conflict": {
"symfony/console": "<3.4"
@ -3613,7 +3497,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2018-04-03T05:14:20+00:00"
"time": "2018-09-24T08:15:45+00:00"
},
{
"name": "webmozart/assert",
@ -3669,8 +3553,7 @@
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"lightopenid/lightopenid": 20,
"pear/text_highlighter": 20
"lightopenid/lightopenid": 20
},
"prefer-stable": false,
"prefer-lowest": false,

View file

@ -140,6 +140,10 @@ disable_url_validation = false
; Disable the exposition check against the remote haveibeenpwned API on password change.
disable_password_exposed = false
; disable_polling (Boolean)
; Disable the polling of DFRN and OStatus contacts through onepoll.php.
disable_polling = false
; dlogfile (Path)
; location of the developer log file.
dlogfile =

View file

@ -15,6 +15,34 @@
"name": ["UNIQUE", "name"]
}
},
"apcontact": {
"comment": "ActivityPub compatible contacts - used in the ActivityPub implementation",
"fields": {
"url": {"type": "varbinary(255)", "not null": "1", "primary": "1", "comment": "URL of the contact"},
"uuid": {"type": "varchar(255)", "comment": ""},
"type": {"type": "varchar(20)", "not null": "1", "comment": ""},
"following": {"type": "varchar(255)", "comment": ""},
"followers": {"type": "varchar(255)", "comment": ""},
"inbox": {"type": "varchar(255)", "not null": "1", "comment": ""},
"outbox": {"type": "varchar(255)", "comment": ""},
"sharedinbox": {"type": "varchar(255)", "comment": ""},
"nick": {"type": "varchar(255)", "not null": "1", "default": "", "comment": ""},
"name": {"type": "varchar(255)", "comment": ""},
"about": {"type": "text", "comment": ""},
"photo": {"type": "varchar(255)", "comment": ""},
"addr": {"type": "varchar(255)", "comment": ""},
"alias": {"type": "varchar(255)", "comment": ""},
"pubkey": {"type": "text", "comment": ""},
"baseurl": {"type": "varchar(255)", "comment": "baseurl of the ap contact"},
"updated": {"type": "datetime", "not null": "1", "default": "0001-01-01 00:00:00", "comment": ""}
},
"indexes": {
"PRIMARY": ["url"],
"addr": ["addr(32)"],
"url": ["followers(190)"]
}
},
"attach": {
"comment": "file attachments",
"fields": {
@ -164,6 +192,7 @@
"hidden": {"type": "boolean", "not null": "1", "default": "0", "comment": ""},
"archive": {"type": "boolean", "not null": "1", "default": "0", "comment": ""},
"pending": {"type": "boolean", "not null": "1", "default": "1", "comment": ""},
"deleted": {"type": "boolean", "not null": "1", "default": "0", "comment": "Contact has been deleted"},
"rating": {"type": "tinyint", "not null": "1", "default": "0", "comment": ""},
"reason": {"type": "text", "comment": ""},
"closeness": {"type": "tinyint unsigned", "not null": "1", "default": "99", "comment": ""},
@ -215,7 +244,7 @@
"reply-to-uri": {"type": "varbinary(255)", "not null": "1", "default": "", "comment": "URI to which this item is a reply"},
"conversation-uri": {"type": "varbinary(255)", "not null": "1", "default": "", "comment": "GNU Social conversation URI"},
"conversation-href": {"type": "varbinary(255)", "not null": "1", "default": "", "comment": "GNU Social conversation link"},
"protocol": {"type": "tinyint unsigned", "not null": "1", "default": "0", "comment": "The protocol of the item"},
"protocol": {"type": "tinyint unsigned", "not null": "1", "default": "255", "comment": "The protocol of the item"},
"source": {"type": "mediumtext", "comment": "Original source"},
"received": {"type": "datetime", "not null": "1", "default": "0001-01-01 00:00:00", "comment": "Receiving date"}
},
@ -225,6 +254,16 @@
"received": ["received"]
}
},
"diaspora-interaction": {
"comment": "Signed Diaspora Interaction",
"fields": {
"uri-id": {"type": "int unsigned", "not null": "1", "primary": "1", "relation": {"item-uri": "id"}, "comment": "Id of the item-uri table entry that contains the item uri"},
"interaction": {"type": "mediumtext", "comment": "The Diaspora interaction"}
},
"indexes": {
"PRIMARY": ["uri-id"]
}
},
"event": {
"comment": "Events",
"fields": {
@ -581,7 +620,8 @@
"indexes": {
"PRIMARY": ["id"],
"uri-hash": ["UNIQUE", "uri-hash"],
"uri": ["uri(191)"]
"uri": ["uri(191)"],
"uri-id": ["uri-id"]
}
},
"item-content": {
@ -610,7 +650,8 @@
"indexes": {
"PRIMARY": ["id"],
"uri-plink-hash": ["UNIQUE", "uri-plink-hash"],
"uri": ["uri(191)"]
"uri": ["uri(191)"],
"uri-id": ["uri-id"]
}
},
"item-delivery-data": {
@ -1269,14 +1310,16 @@
"created": {"type": "datetime", "not null": "1", "default": "0001-01-01 00:00:00", "comment": "Creation date"},
"pid": {"type": "int unsigned", "not null": "1", "default": "0", "comment": "Process id of the worker"},
"executed": {"type": "datetime", "not null": "1", "default": "0001-01-01 00:00:00", "comment": "Execution date"},
"next_try": {"type": "datetime", "not null": "1", "default": "0001-01-01 00:00:00", "comment": "Next retrial date"},
"retrial": {"type": "tinyint", "not null": "1", "default": "0", "comment": "Retrial counter"},
"done": {"type": "boolean", "not null": "1", "default": "0", "comment": "Marked 1 when the task was done - will be deleted later"}
},
"indexes": {
"PRIMARY": ["id"],
"pid": ["pid"],
"parameter": ["parameter(64)"],
"priority_created": ["priority", "created"],
"done_executed": ["done", "executed"]
"priority_created_next_try": ["priority", "created", "next_try"],
"done_executed_next_try": ["done", "executed", "next_try"]
}
}
}

View file

@ -1,6 +1,6 @@
-- ------------------------------------------
-- Friendica 2018.08-dev (The Tazmans Flax-lily)
-- DB_UPDATE_VERSION 1283
-- Friendica 2018.12-dev (The Tazmans Flax-lily)
-- DB_UPDATE_VERSION 1287
-- ------------------------------------------
@ -19,6 +19,32 @@ CREATE TABLE IF NOT EXISTS `addon` (
UNIQUE INDEX `name` (`name`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='registered addons';
--
-- TABLE apcontact
--
CREATE TABLE IF NOT EXISTS `apcontact` (
`url` varbinary(255) NOT NULL COMMENT 'URL of the contact',
`uuid` varchar(255) COMMENT '',
`type` varchar(20) NOT NULL COMMENT '',
`following` varchar(255) COMMENT '',
`followers` varchar(255) COMMENT '',
`inbox` varchar(255) NOT NULL COMMENT '',
`outbox` varchar(255) COMMENT '',
`sharedinbox` varchar(255) COMMENT '',
`nick` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`name` varchar(255) COMMENT '',
`about` text COMMENT '',
`photo` varchar(255) COMMENT '',
`addr` varchar(255) COMMENT '',
`alias` varchar(255) COMMENT '',
`pubkey` text COMMENT '',
`baseurl` varchar(255) COMMENT 'baseurl of the ap contact',
`updated` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
PRIMARY KEY(`url`),
INDEX `addr` (`addr`(32)),
INDEX `url` (`followers`(190))
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='ActivityPub compatible contacts - used in the ActivityPub implementation';
--
-- TABLE attach
--
@ -163,6 +189,7 @@ CREATE TABLE IF NOT EXISTS `contact` (
`hidden` boolean NOT NULL DEFAULT '0' COMMENT '',
`archive` boolean NOT NULL DEFAULT '0' COMMENT '',
`pending` boolean NOT NULL DEFAULT '1' COMMENT '',
`deleted` boolean NOT NULL DEFAULT '0' COMMENT 'Contact has been deleted',
`rating` tinyint NOT NULL DEFAULT 0 COMMENT '',
`reason` text COMMENT '',
`closeness` tinyint unsigned NOT NULL DEFAULT 99 COMMENT '',
@ -212,7 +239,7 @@ CREATE TABLE IF NOT EXISTS `conversation` (
`reply-to-uri` varbinary(255) NOT NULL DEFAULT '' COMMENT 'URI to which this item is a reply',
`conversation-uri` varbinary(255) NOT NULL DEFAULT '' COMMENT 'GNU Social conversation URI',
`conversation-href` varbinary(255) NOT NULL DEFAULT '' COMMENT 'GNU Social conversation link',
`protocol` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'The protocol of the item',
`protocol` tinyint unsigned NOT NULL DEFAULT 255 COMMENT 'The protocol of the item',
`source` mediumtext COMMENT 'Original source',
`received` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Receiving date',
PRIMARY KEY(`item-uri`),
@ -220,6 +247,15 @@ CREATE TABLE IF NOT EXISTS `conversation` (
INDEX `received` (`received`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Raw data and structure information for messages';
--
-- TABLE diaspora-interaction
--
CREATE TABLE IF NOT EXISTS `diaspora-interaction` (
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri',
`interaction` mediumtext COMMENT 'The Diaspora interaction',
PRIMARY KEY(`uri-id`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Signed Diaspora Interaction';
--
-- TABLE event
--
@ -1215,12 +1251,14 @@ CREATE TABLE IF NOT EXISTS `workerqueue` (
`created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Creation date',
`pid` int unsigned NOT NULL DEFAULT 0 COMMENT 'Process id of the worker',
`executed` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Execution date',
`next_try` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Next retrial date',
`retrial` tinyint NOT NULL DEFAULT 0 COMMENT 'Retrial counter',
`done` boolean NOT NULL DEFAULT '0' COMMENT 'Marked 1 when the task was done - will be deleted later',
PRIMARY KEY(`id`),
INDEX `pid` (`pid`),
INDEX `parameter` (`parameter`(64)),
INDEX `priority_created` (`priority`,`created`),
INDEX `done_executed` (`done`,`executed`)
INDEX `priority_created_next_try` (`priority`,`created`,`next_try`),
INDEX `done_executed_next_try` (`done`,`executed`,`next_try`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Background tasks queue entries';

View file

@ -67,55 +67,67 @@ $b can be called anything you like.
This is information specific to the hook currently being processed, and generally contains information that is being immediately processed or acted on that you can use, display, or alter.
Remember to declare it with `&` if you wish to alter it.
## JavaScript addon hooks
## Global stylesheets
### PHP part
Make sure your JavaScript addon file (addon/*addon_name*/*addon_name*.js) is listed in the document response.
In your addon install function, add:
If your addon requires adding a stylesheet on all pages of Friendica, add the following hook:
```php
Addon::registerHook('template_vars', __FILE__, '<addon_name>_template_vars');
```
In your addon uninstall function, add:
```php
Addon::unregisterHook('template_vars', __FILE__, '<addon_name>_template_vars');
```
Then, add your addon name to the *addon_hooks* template variable array:
```php
function <addon_name>_template_vars($a, &$arr)
function <addon>_install()
{
if (!array_key_exists('addon_hooks', $arr['vars']))
{
$arr['vars']['addon_hooks'] = array();
}
$arr['vars']['addon_hooks'][] = "<addon_name>";
Addon::registerHook('head', __FILE__, '<addon>_head');
...
}
function <addon>_head(App $a)
{
$a->registerStylesheet(__DIR__ . '/relative/path/to/addon/stylesheet.css');
}
```
### JavaScript part
`__DIR__` is the folder path of your addon.
Register your addon hooks in file `addon/*addon_name*/*addon_name*.js`.
## JavaScript
### Global scripts
If your addon requires adding a script on all pages of Friendica, add the following hook:
```php
function <addon>_install()
{
Addon::registerHook('footer', __FILE__, '<addon>_footer');
...
}
function <addon>_footer(App $a)
{
$a->registerFooterScript(__DIR__ . '/relative/path/to/addon/script.js');
}
```
`__DIR__` is the folder path of your addon.
### JavaScript hooks
The main Friendica script provides hooks via events dispatched on the `document` property.
In your Javascript file included as described above, add your event listener like this:
```js
Addon_registerHook(type, hookfnstr);
document.addEventListener(name, callback);
```
*type* is the name of the hook and corresponds to a known Friendica JavaScript hook.
*hookfnstr* is the name of your JavaScript function to execute.
- *name* is the name of the hook and corresponds to a known Friendica JavaScript hook.
- *callback* is a JavaScript anonymous function to execute.
No arguments are provided to your JavaScript callback function. Example:
More info about Javascript event listeners: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
```javascript
function myhook_function() {
#### Current JavaScript hooks
}
```
##### postprocess_liveupdate
Called at the end of the live update process (XmlHttpRequest) and on a post preview.
No additional data is provided.
## Modules
@ -260,6 +272,11 @@ Called after conversion of bbcode to HTML.
Called after tag conversion of HTML to bbcode (e.g. remote message posting)
`$b` is a string converted text
### head
Called when building the `<head>` sections.
Stylesheets should be registered using this hook.
`$b` is an HTML string of the `<head>` tag.
### page_header
Called after building the page navigation section.
`$b` is a string HTML of nav region.
@ -294,6 +311,11 @@ No hook data.
Called after HTML content functions have completed.
`$b` is (string) HTML of content div.
### footer
Called after HTML content functions have completed.
Deferred Javascript files should be registered using this hook.
`$b` is (string) HTML of footer div/element.
### avatar_lookup
Called when looking up the avatar. `$b` is an array:
@ -389,14 +411,9 @@ Hook data:
visitor => array with the contact record of the visitor
url => the query string
## Current JavaScript hooks
### postprocess_liveupdate
Called at the end of the live update process (XmlHttpRequest)
## Complete list of hook callbacks
Here is a complete list of all hook callbacks with file locations (as of 01-Apr-2018). Please see the source for details of any hooks not documented above.
Here is a complete list of all hook callbacks with file locations (as of 24-Sep-2018). Please see the source for details of any hooks not documented above.
### index.php
@ -571,6 +588,8 @@ Here is a complete list of all hook callbacks with file locations (as of 01-Apr-
### src/App.php
Addon::callHooks('load_config');
Addon::callHooks('head');
Addon::callHooks('footer');
### src/Model/Item.php
@ -704,4 +723,4 @@ Here is a complete list of all hook callbacks with file locations (as of 01-Apr-
### view/js/main.js
callAddonHooks("postprocess_liveupdate");
document.dispatchEvent(new Event('postprocess_liveupdate'));

View file

@ -33,7 +33,7 @@ If you don't want to translate the UI, or it is already done to your satisfactio
Are you good at designing things?
If you have seen Friendica you probably have ideas to improve it, haven't you?
* If you would like to work with us on enhancing the user interface, please join the [UX Watchdogs forum](https://fc.oscp.info/profile/ux-watchdogs)
* If you would like to work with us on enhancing the user interface, please join the [forum for Friendica development](https://forum.friendi.ca/profile/developers).
* Make plans for a better Friendica interface design and share them with us.
* Tell us if you are able to realize your ideas or what kind of help you need.
We can't promise we have the right skills in the group but we'll try.

View file

@ -40,6 +40,14 @@ Requirements
Installation procedure
---
### Alternative Installation Methods
This guide will walk you through the manual installation process of Friendica.
If this is nothing for you, you might be interested in
* the [Friendica Docker image](https://github.com/friendica/docker) or
* how [install Friendica with YunoHost](https://github.com/YunoHost-Apps/friendica_ynh).
### Get Friendica
Unpack the Friendica files into the root of your web server document area.
@ -164,6 +172,7 @@ if you don't use the option `--savedb` during installation, the DB credentials w
This variables wont be used at normal Friendica runtime.
Instead, they get saved into `config/local.ini.php`.
- `FRIENDICA_URL_PATH` The URL path of Friendica (f.e. '/friendica')
- `FRIENDICA_PHP_PATH` The path of the PHP binary
- `FRIENDICA_ADMIN_MAIL` The admin email address of Friendica (this email will be used for admin access)
- `FRIENDICA_TZ` The timezone of Friendica
@ -182,7 +191,8 @@ All options will be saved in the `config/local.ini.php` and are overruling the a
- `-U|--dbuser <username>` The username of the mysql/mariadb database login (env `MYSQL_USER` or `MYSQL_USERNAME`)
- `-P|--dbpass <password>` The password of the mysql/mariadb database login (env `MYSQL_PASSWORD`)
- `-d|--dbdata <database>` The name of the mysql/mariadb database (env `MYSQL_DATABASE`)
- `-b|--phppath <path>` The path of the PHP binary (env `FRIENDICA_PHP_PATH`)
- `-u|--urlpath <url_path>` The URL path of Friendica - f.e. '/friendica' (env `FRIENDICA_URL_PATH`)
- `-b|--phppath <php_path>` The path of the PHP binary (env `FRIENDICA_PHP_PATH`)
- `-A|--admin <mail>` The admin email address of Friendica (env `FRIENDICA_ADMIN_MAIL`)
- `-T|--tz <timezone>` The timezone of Friendica (env `FRIENDICA_TZ`)
- `-L|--land <language>` The language of Friendica (env `FRIENDICA_LANG`)

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>.
<iframe src="http://dir.friendica.com/directory/forum" width="950" height="600"></iframe>
<iframe src="http://dir.friendica.social/directory" width="950" height="600"></iframe>

View file

@ -180,7 +180,7 @@ Hier ist eine Liste von Clients bei denen dies möglich ist, bzw. die speziell f
<a name="help"></a>
### Wo finde ich Hilfe?
Wenn Du Probleme mit Deiner Friendica-Seite hast, dann kannst Du die Community in der [Friendica-Support-Gruppe](https://forum.friendi.ca/profile/helpers) oder im [deutschen Friendica-Support-Forum](http://toktan.org/profile/wiki) fragen oder Dir das [deutsche Wiki](http://wiki.toktan.org/doku.php) anschauen.
Wenn Du Probleme mit Deiner Friendica-Seite hast, dann kannst Du die Community in der [Friendica-Support-Gruppe](https://forum.friendi.ca/profile/helpers) fragen oder Dir das [deutsche Wiki](https://friendica-wiki.de/) anschauen.
Wenn Du Deinen Account nicht nutzen kannst, kannst Du entweder einen [Testaccount](https://tryfriendica.de) bzw. einen Account auf einer öffentlichen Seite ([Liste](https://dir.friendica.social/servers)) nutzen.
Wenn du dir keinen weiteren Friendica Account einrichten willst, kannst du auch gerne über einen der folgenden alternativen Kanäle Hilfe suchen:

View file

@ -61,7 +61,7 @@ Friendica - Dokumentation und Ressourcen
**Externe Ressourcen**
* [Haupt-Webseite](https://friendi.ca)
* [Deutsches Friendica-Wiki](http://wiki.toktan.org/doku.php)
* [Deutsches Friendica-Wiki](https://friendica-wiki.de)
* Support Kanäle
* [Friendica Support Forum](https://forum.friendi.ca/~helpers)
* [Mailing Listen Archiv](http://mailman.friendi.ca/mailman/listinfo/support-friendi.ca) zum Abonnieren der Liste eine E-Mail an ``support-request(at)friendi.ca?subject=subscribe`` senden

View file

@ -40,6 +40,14 @@ Requirements
Installation
---
### Alternative Wege um Friendica zu Installieren
Diese Anleitung wird dir Schritt-für-Schritt zeigen wie du Friendica auf deinem Server installieren kannst.
Falls du an automatischen Möglichkeiten interesse hast, wirf doch einen Blick auf
* das [Docker image für Friendica](https://github.com/friendica/docker) oder
* die [Installation von Friendica auf YunoHost](https://github.com/YunoHost-Apps/friendica_ynh).
### Friendica
Entpacke die Friendica-Daten in das Quellverzeichnis (root) des Dokumentenbereichs deines Webservers.
@ -82,7 +90,7 @@ Dies tust du mit den folgenden Befehlen
Die Entwickler Version kann nach einem fehlerhaften Commit vorübergehend Probleme haben oder gar nicht mehr funktionieren.
Sollte dir so etwas passieren, lass es uns bitte wissen, damit der Fehler behoben werden kann.
### Erselle eine Datenbank
### Erstelle eine Datenbank
Erstelle eine leere Datenbank und notiere alle Zugangsdaten (Adresse der Datenbank, Nutzername, Passwort, Datenbankname).

View file

@ -581,7 +581,10 @@ function api_get_user(App $a, $contact_id = null)
if (is_null($user) && ($a->argc > (count($called_api) - 1)) && (count($called_api) > 0)) {
$argid = count($called_api);
if (!empty($a->argv[$argid])) {
list($user, $null) = explode(".", $a->argv[$argid]);
$data = explode(".", $a->argv[$argid]);
if (count($data) > 1) {
list($user, $null) = $data;
}
}
if (is_numeric($user)) {
$user = DBA::escape(api_unique_id_to_nurl(intval($user)));
@ -758,7 +761,7 @@ function api_get_user(App $a, $contact_id = null)
'statusnet_blocking' => false,
'notifications' => false,
/// @TODO old way?
//'statusnet_profile_url' => System::baseUrl()."/contacts/".$uinfo[0]['cid'],
//'statusnet_profile_url' => System::baseUrl()."/contact/".$uinfo[0]['cid'],
'statusnet_profile_url' => $uinfo[0]['url'],
'uid' => intval($uinfo[0]['uid']),
'cid' => intval($uinfo[0]['cid']),
@ -816,7 +819,7 @@ function api_item_get_user(App $a, $item)
$status_user["protected"] = defaults($item, 'private', 0);
if (defaults($item, 'thr-parent', '') == defaults($item, 'uri', '')) {
$owner_user = api_get_user($a, defaults($item, 'author-id', null));
$owner_user = api_get_user($a, defaults($item, 'owner-id', null));
} else {
$owner_user = $status_user;
}
@ -1056,10 +1059,10 @@ function api_statuses_mediap($type)
// now that we have the img url in bbcode we can add it to the status and insert the wall item.
$_REQUEST['body'] = $txt . "\n\n" . '[url=' . $picture["albumpage"] . '][img]' . $picture["preview"] . "[/img][/url]";
item_post($a);
$item_id = item_post($a);
// this should output the last post (the one we just posted).
return api_status_show($type);
// output the post that we just posted.
return api_status_show($type, $item_id);
}
/// @TODO move this to top of file or somewhere better!
@ -1075,7 +1078,6 @@ api_register_func('api/statuses/mediap', 'api_statuses_mediap', true, API_METHOD
*/
function api_statuses_update($type)
{
$a = get_app();
if (api_user() === false) {
@ -1129,8 +1131,8 @@ function api_statuses_update($type)
if ($throttle_day > 0) {
$datefrom = date(DateTimeFormat::MYSQL, time() - 24*60*60);
$condition = ["`uid` = ? AND `wall` AND `created` > ? AND `id` = `parent`", api_user(), $datefrom];
$posts_day = DBA::count('item', $condition);
$condition = ["`uid` = ? AND `wall` AND `created` > ?", api_user(), $datefrom];
$posts_day = DBA::count('thread', $condition);
if ($posts_day > $throttle_day) {
logger('Daily posting limit reached for user '.api_user(), LOGGER_DEBUG);
@ -1143,8 +1145,8 @@ function api_statuses_update($type)
if ($throttle_week > 0) {
$datefrom = date(DateTimeFormat::MYSQL, time() - 24*60*60*7);
$condition = ["`uid` = ? AND `wall` AND `created` > ? AND `id` = `parent`", api_user(), $datefrom];
$posts_week = DBA::count('item', $condition);
$condition = ["`uid` = ? AND `wall` AND `created` > ?", api_user(), $datefrom];
$posts_week = DBA::count('thread', $condition);
if ($posts_week > $throttle_week) {
logger('Weekly posting limit reached for user '.api_user(), LOGGER_DEBUG);
@ -1157,8 +1159,8 @@ function api_statuses_update($type)
if ($throttle_month > 0) {
$datefrom = date(DateTimeFormat::MYSQL, time() - 24*60*60*30);
$condition = ["`uid` = ? AND `wall` AND `created` > ? AND `id` = `parent`", api_user(), $datefrom];
$posts_month = DBA::count('item', $condition);
$condition = ["`uid` = ? AND `wall` AND `created` > ?", api_user(), $datefrom];
$posts_month = DBA::count('thread', $condition);
if ($posts_month > $throttle_month) {
logger('Monthly posting limit reached for user '.api_user(), LOGGER_DEBUG);
@ -1200,10 +1202,10 @@ function api_statuses_update($type)
}
// call out normal post function
item_post($a);
$item_id = item_post($a);
// this should output the last post (the one we just posted).
return api_status_show($type);
// output the post that we just posted.
return api_status_show($type, $item_id);
}
/// @TODO move to top of file or somewhere better
@ -1260,7 +1262,7 @@ api_register_func('api/media/upload', 'api_media_upload', true, API_METHOD_POST)
*
* @return array|string
*/
function api_status_show($type)
function api_status_show($type, $item_id = 0)
{
$a = get_app();
@ -1274,9 +1276,14 @@ function api_status_show($type)
$privacy_sql = "";
}
// get last public wall message
$condition = ['owner-id' => $user_info['pid'], 'uid' => api_user(),
'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT]];
if (!empty($item_id)) {
// Get the item with the given id
$condition = ['id' => $item_id];
} else {
// get last public wall message
$condition = ['owner-id' => $user_info['pid'], 'uid' => api_user(),
'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT]];
}
$lastwall = Item::selectFirst(Item::ITEM_FIELDLIST, $condition, ['order' => ['id' => true]]);
if (DBA::isResult($lastwall)) {
@ -1993,14 +2000,14 @@ function api_statuses_repeat($type)
$_REQUEST["source"] = api_source();
}
item_post($a);
$item_id = item_post($a);
} else {
throw new ForbiddenException();
}
// this should output the last post (the one we just posted).
// output the post that we just posted.
$called_api = [];
return api_status_show($type);
return api_status_show($type, $item_id);
}
/// @TODO move to top of file or somewhere better
@ -2344,7 +2351,7 @@ function api_format_messages($item, $recipient, $sender)
// standard meta information
$ret = [
'id' => $item['id'],
'sender_id' => $sender['id'] ,
'sender_id' => $sender['id'],
'text' => "",
'recipient_id' => $recipient['id'],
'created_at' => api_date(defaults($item, 'created', DateTimeFormat::utcNow())),
@ -2725,7 +2732,7 @@ function api_contactlink_to_array($txt)
* likes => int count,
* dislikes => int count
*/
function api_format_items_activities(&$item, $type = "json")
function api_format_items_activities($item, $type = "json")
{
$a = get_app();
@ -2740,13 +2747,13 @@ function api_format_items_activities(&$item, $type = "json")
$condition = ['uid' => $item['uid'], 'thr-parent' => $item['uri']];
$ret = Item::selectForUser($item['uid'], ['author-id', 'verb'], $condition);
while ($item = Item::fetch($ret)) {
while ($parent_item = Item::fetch($ret)) {
// not used as result should be structured like other user data
//builtin_activity_puller($i, $activities);
// get user data and add it to the array of the activity
$user = api_get_user($a, $item['author-id']);
switch ($item['verb']) {
$user = api_get_user($a, $parent_item['author-id']);
switch ($parent_item['verb']) {
case ACTIVITY_LIKE:
$activities['like'][] = $user;
break;
@ -2886,7 +2893,7 @@ function api_format_items($r, $user_info, $filter_user = false, $type = "json")
'in_reply_to_screen_name' => $in_reply_to['screen_name'],
$geo => null,
'favorited' => $item['starred'] ? true : false,
'user' => $status_user ,
'user' => $status_user,
'friendica_owner' => $owner_user,
'friendica_private' => $item['private'] == 1,
//'entities' => NULL,
@ -3337,7 +3344,7 @@ function api_statusnet_config($type)
$a = get_app();
$name = Config::get('config', 'sitename');
$server = $a->get_hostname();
$server = $a->getHostName();
$logo = System::baseUrl() . '/images/friendica-64.png';
$email = Config::get('config', 'admin_email');
$closed = intval(Config::get('config', 'register_policy')) === REGISTER_CLOSED ? 'true' : 'false';
@ -3394,7 +3401,7 @@ api_register_func('api/statusnet/version', 'api_statusnet_version', false);
*/
function api_ff_ids($type)
{
if (! api_user()) {
if (!api_user()) {
throw new ForbiddenException();
}
@ -3622,6 +3629,84 @@ function api_direct_messages_destroy($type)
/// @TODO move to top of file or somewhere better
api_register_func('api/direct_messages/destroy', 'api_direct_messages_destroy', true, API_METHOD_DELETE);
/**
* Unfollow Contact
*
* @brief unfollow contact
*
* @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
* @return string|array
* @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/post-friendships-destroy.html
*/
function api_friendships_destroy($type)
{
$uid = api_user();
if ($uid === false) {
throw new ForbiddenException();
}
$contact_id = defaults($_REQUEST, 'user_id');
if (empty($contact_id)) {
logger("No user_id specified", LOGGER_DEBUG);
throw new BadRequestException("no user_id specified");
}
// Get Contact by given id
$contact = DBA::selectFirst('contact', ['url'], ['id' => $contact_id, 'uid' => 0, 'self' => false]);
if(!DBA::isResult($contact)) {
logger("No contact found for ID" . $contact_id, LOGGER_DEBUG);
throw new NotFoundException("no contact found to given ID");
}
$url = $contact["url"];
$condition = ["`uid` = ? AND (`rel` = ? OR `rel` = ?) AND (`nurl` = ? OR `alias` = ? OR `alias` = ?)",
$uid, Contact::SHARING, Contact::FRIEND, normalise_link($url),
normalise_link($url), $url];
$contact = DBA::selectFirst('contact', [], $condition);
if (!DBA::isResult($contact)) {
logger("Not following Contact", LOGGER_DEBUG);
throw new NotFoundException("Not following Contact");
}
if (!in_array($contact['network'], Protocol::NATIVE_SUPPORT)) {
logger("Not supported", LOGGER_DEBUG);
throw new ExpectationFailedException("Not supported");
}
$dissolve = ($contact['rel'] == Contact::SHARING);
$owner = User::getOwnerDataById($uid);
if ($owner) {
Contact::terminateFriendship($owner, $contact, $dissolve);
}
else {
logger("No owner found", LOGGER_DEBUG);
throw new NotFoundException("Error Processing Request");
}
// Sharing-only contacts get deleted as there no relationship any more
if ($dissolve) {
Contact::remove($contact['id']);
} else {
DBA::update('contact', ['rel' => Contact::FOLLOWER], ['id' => $contact['id']]);
}
// "uid" and "self" are only needed for some internal stuff, so remove it from here
unset($contact["uid"]);
unset($contact["self"]);
// Set screen_name since Twidere requests it
$contact["screen_name"] = $contact["nick"];
return api_format_data("friendships-destroy", $type, ['user' => $contact]);
}
api_register_func('api/friendships/destroy', 'api_friendships_destroy', true, API_METHOD_POST);
/**
*
* @param string $type Return type (atom, rss, xml, json)
@ -4396,7 +4481,7 @@ function save_media_to_database($mediatype, $media, $type, $album, $allow_cid, $
if ($filetype == "") {
$filetype=Image::guessType($filename);
}
$imagedata = getimagesize($src);
$imagedata = @getimagesize($src);
if ($imagedata) {
$filetype = $imagedata['mime'];
}
@ -4420,7 +4505,7 @@ function save_media_to_database($mediatype, $media, $type, $album, $allow_cid, $
// create Photo instance with the data of the image
$imagedata = @file_get_contents($src);
$Image = new Image($imagedata, $filetype);
if (! $Image->isValid()) {
if (!$Image->isValid()) {
throw new InternalServerErrorException("unable to process image data");
}
@ -4430,7 +4515,7 @@ function save_media_to_database($mediatype, $media, $type, $album, $allow_cid, $
// check max length of images on server
$max_length = Config::get('system', 'max_image_length');
if (! $max_length) {
if (!$max_length) {
$max_length = MAX_IMAGE_LENGTH;
}
if ($max_length > 0) {
@ -4448,13 +4533,13 @@ function save_media_to_database($mediatype, $media, $type, $album, $allow_cid, $
logger("photo upload: starting new photo upload", LOGGER_DEBUG);
$r = Photo::store($Image, local_user(), $visitor, $hash, $filename, $album, 0, 0, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
if (! $r) {
if (!$r) {
logger("photo upload: image upload with scale 0 (original size) failed");
}
if ($width > 640 || $height > 640) {
$Image->scaleDown(640);
$r = Photo::store($Image, local_user(), $visitor, $hash, $filename, $album, 1, 0, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
if (! $r) {
if (!$r) {
logger("photo upload: image upload with scale 1 (640x640) failed");
}
}
@ -4462,7 +4547,7 @@ function save_media_to_database($mediatype, $media, $type, $album, $allow_cid, $
if ($width > 320 || $height > 320) {
$Image->scaleDown(320);
$r = Photo::store($Image, local_user(), $visitor, $hash, $filename, $album, 2, 0, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
if (! $r) {
if (!$r) {
logger("photo upload: image upload with scale 2 (320x320) failed");
}
}
@ -4474,7 +4559,7 @@ function save_media_to_database($mediatype, $media, $type, $album, $allow_cid, $
if ($width > 175 || $height > 175) {
$Image->scaleDown(175);
$r = Photo::store($Image, local_user(), $visitor, $hash, $filename, $album, 4, $profile, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
if (! $r) {
if (!$r) {
logger("photo upload: profile image upload with scale 4 (175x175) failed");
}
}
@ -4482,7 +4567,7 @@ function save_media_to_database($mediatype, $media, $type, $album, $allow_cid, $
if ($width > 80 || $height > 80) {
$Image->scaleDown(80);
$r = Photo::store($Image, local_user(), $visitor, $hash, $filename, $album, 5, $profile, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
if (! $r) {
if (!$r) {
logger("photo upload: profile image upload with scale 5 (80x80) failed");
}
}
@ -4490,7 +4575,7 @@ function save_media_to_database($mediatype, $media, $type, $album, $allow_cid, $
if ($width > 48 || $height > 48) {
$Image->scaleDown(48);
$r = Photo::store($Image, local_user(), $visitor, $hash, $filename, $album, 6, $profile, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
if (! $r) {
if (!$r) {
logger("photo upload: profile image upload with scale 6 (48x48) failed");
}
}
@ -4527,7 +4612,7 @@ function post_photo_item($hash, $allow_cid, $deny_cid, $allow_gid, $deny_gid, $f
$owner_record = DBA::selectFirst('contact', [], ['uid' => api_user(), 'self' => true]);
$arr = [];
$arr['guid'] = System::createGUID(32);
$arr['guid'] = System::createUUID();
$arr['uid'] = intval(api_user());
$arr['uri'] = $uri;
$arr['parent-uri'] = $uri;
@ -4742,77 +4827,86 @@ function api_share_as_retweet(&$item)
{
$body = trim($item["body"]);
if (Diaspora::isReshare($body, false)===false) {
return false;
if (Diaspora::isReshare($body, false) === false) {
if ($item['author-id'] == $item['owner-id']) {
return false;
} else {
// Reshares from OStatus, ActivityPub and Twitter
$reshared_item = $item;
$reshared_item['owner-id'] = $reshared_item['author-id'];
$reshared_item['owner-link'] = $reshared_item['author-link'];
$reshared_item['owner-name'] = $reshared_item['author-name'];
$reshared_item['owner-avatar'] = $reshared_item['author-avatar'];
return $reshared_item;
}
}
/// @TODO "$1" should maybe mean '$1' ?
$attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "$1", $body);
/*
* Skip if there is no shared message in there
* we already checked this in diaspora::isReshare()
* but better one more than one less...
*/
if ($body == $attributes) {
* Skip if there is no shared message in there
* we already checked this in diaspora::isReshare()
* but better one more than one less...
*/
if (($body == $attributes) || empty($attributes)) {
return false;
}
// build the fake reshared item
$reshared_item = $item;
$author = "";
preg_match("/author='(.*?)'/ism", $attributes, $matches);
if ($matches[1] != "") {
if (!empty($matches[1])) {
$author = html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8');
}
preg_match('/author="(.*?)"/ism', $attributes, $matches);
if ($matches[1] != "") {
if (!empty($matches[1])) {
$author = $matches[1];
}
$profile = "";
preg_match("/profile='(.*?)'/ism", $attributes, $matches);
if ($matches[1] != "") {
if (!empty($matches[1])) {
$profile = $matches[1];
}
preg_match('/profile="(.*?)"/ism', $attributes, $matches);
if ($matches[1] != "") {
if (!empty($matches[1])) {
$profile = $matches[1];
}
$avatar = "";
preg_match("/avatar='(.*?)'/ism", $attributes, $matches);
if ($matches[1] != "") {
if (!empty($matches[1])) {
$avatar = $matches[1];
}
preg_match('/avatar="(.*?)"/ism', $attributes, $matches);
if ($matches[1] != "") {
if (!empty($matches[1])) {
$avatar = $matches[1];
}
$link = "";
preg_match("/link='(.*?)'/ism", $attributes, $matches);
if ($matches[1] != "") {
if (!empty($matches[1])) {
$link = $matches[1];
}
preg_match('/link="(.*?)"/ism', $attributes, $matches);
if ($matches[1] != "") {
if (!empty($matches[1])) {
$link = $matches[1];
}
$posted = "";
preg_match("/posted='(.*?)'/ism", $attributes, $matches);
if ($matches[1] != "") {
if (!empty($matches[1])) {
$posted = $matches[1];
}
preg_match('/posted="(.*?)"/ism', $attributes, $matches);
if ($matches[1] != "") {
if (!empty($matches[1])) {
$posted = $matches[1];
}

View file

@ -229,12 +229,12 @@ function localize_item(&$item)
$xmlhead = "<" . "?xml version='1.0' encoding='UTF-8' ?" . ">";
$obj = XML::parseString($xmlhead.$item['object']);
$links = XML::parseString($xmlhead."<links>".unxmlify($obj->link)."</links>");
$Bname = $obj->title;
$Blink = "";
$Blink = $obj->id;
$Bphoto = "";
foreach ($links->link as $l) {
foreach ($obj->link as $l) {
$atts = $l->attributes();
switch ($atts['rel']) {
case "alternate": $Blink = $atts['href'];
@ -556,7 +556,7 @@ function conversation(App $a, array $items, $mode, $update, $preview = false, $o
if (in_array($mode, ['community', 'contacts'])) {
$writable = true;
} else {
$writable = ($items[0]['uid'] == 0) && in_array($items[0]['network'], [Protocol::OSTATUS, Protocol::DIASPORA, Protocol::DFRN]);
$writable = ($items[0]['uid'] == 0) && in_array($items[0]['network'], [Protocol::ACTIVITYPUB, Protocol::OSTATUS, Protocol::DIASPORA, Protocol::DFRN]);
}
if (!local_user()) {
@ -657,7 +657,7 @@ function conversation(App $a, array $items, $mode, $update, $preview = false, $o
'id' => ($preview ? 'P0' : $item['id']),
'guid' => ($preview ? 'Q0' : $item['guid']),
'network' => $item['network'],
'network_name' => ContactSelector::networkToName($item['network'], $profile_link),
'network_name' => ContactSelector::networkToName($item['network'], $item['author-link']),
'linktitle' => L10n::t('View %s\'s profile @ %s', $profile_name, $item['author-link']),
'profile_url' => $profile_link,
'item_photo_menu' => item_photo_menu($item),
@ -807,7 +807,7 @@ function conversation_add_children(array $parents, $block_authors, $order, $uid)
foreach ($items as $index => $item) {
if ($item['uid'] == 0) {
$items[$index]['writable'] = in_array($item['network'], [Protocol::OSTATUS, Protocol::DIASPORA, Protocol::DFRN]);
$items[$index]['writable'] = in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::OSTATUS, Protocol::DIASPORA, Protocol::DFRN]);
}
}
@ -853,8 +853,8 @@ function item_photo_menu($item) {
if ($cid && !$item['self']) {
$poke_link = 'poke/?f=&c=' . $cid;
$contact_url = 'contacts/' . $cid;
$posts_link = 'contacts/' . $cid . '/posts';
$contact_url = 'contact/' . $cid;
$posts_link = 'contact/' . $cid . '/posts';
if (in_array($network, [Protocol::DFRN, Protocol::DIASPORA])) {
$pm_url = 'message/new/' . $cid;
@ -877,7 +877,7 @@ function item_photo_menu($item) {
}
if ((($cid == 0) || ($rel == Contact::FOLLOWER)) &&
in_array($item['network'], [Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA])) {
in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA])) {
$menu[L10n::t('Connect/Follow')] = 'follow?url=' . urlencode($item['author-link']);
}
} else {
@ -1091,21 +1091,6 @@ function status_editor(App $a, $x, $notes_cid = 0, $popup = false)
'$delitems' => L10n::t("Delete item\x28s\x29?")
]);
$tpl = get_markup_template('jot-end.tpl');
$a->page['end'] .= replace_macros($tpl, [
'$newpost' => 'true',
'$baseurl' => System::baseUrl(true),
'$geotag' => $geotag,
'$nickname' => $x['nickname'],
'$ispublic' => L10n::t('Visible to <strong>everybody</strong>'),
'$linkurl' => L10n::t('Please enter a link URL:'),
'$vidurl' => L10n::t("Please enter a video link/URL:"),
'$audurl' => L10n::t("Please enter an audio link/URL:"),
'$term' => L10n::t('Tag term:'),
'$fileas' => L10n::t('Save to Folder:'),
'$whereareu' => L10n::t('Where are you right now?')
]);
$jotplugins = '';
Addon::callHooks('jot_tool', $jotplugins);
@ -1454,7 +1439,7 @@ function get_responses(array $conv_responses, array $response_verbs, $ob, array
$ret = [];
foreach ($response_verbs as $v) {
$ret[$v] = [];
$ret[$v]['count'] = defaults($conv_responses[$v], $item['uri'], '');
$ret[$v]['count'] = defaults($conv_responses[$v], $item['uri'], 0);
$ret[$v]['list'] = defaults($conv_responses[$v], $item['uri'] . '-l', []);
$ret[$v]['self'] = defaults($conv_responses[$v], $item['uri'] . '-self', '0');
if (count($ret[$v]['list']) > MAX_LIKERS) {

View file

@ -42,9 +42,9 @@ function notification($params)
}
$params['notify_flags'] = defaults($params, 'notify_flags', $user['notify-flags']);
$params['language'] = defaults($params, 'language', $user['language']);
$params['to_name'] = defaults($params, 'to_name', $user['username']);
$params['to_email'] = defaults($params, 'to_email', $user['email']);
$params['language'] = defaults($params, 'language' , $user['language']);
$params['to_name'] = defaults($params, 'to_name' , $user['username']);
$params['to_email'] = defaults($params, 'to_email' , $user['email']);
// from here on everything is in the recipients language
L10n::pushLang($params['language']);
@ -61,7 +61,7 @@ function notification($params)
}
$sender_name = $sitename;
$hostname = $a->get_hostname();
$hostname = $a->getHostName();
if (strpos($hostname, ':')) {
$hostname = substr($hostname, 0, strpos($hostname, ':'));
}
@ -84,7 +84,7 @@ function notification($params)
// with $params['show_in_notification_page'] == false, the notification isn't inserted into
// the database, and an email is sent if applicable.
// default, if not specified: true
$show_in_notification_page = ((x($params, 'show_in_notification_page')) ? $params['show_in_notification_page']:true);
$show_in_notification_page = isset($params['show_in_notification_page']) ? $params['show_in_notification_page'] : true;
$additional_mail_header = "";
$additional_mail_header .= "Precedence: list\n";

View file

@ -72,7 +72,8 @@ function add_page_info_data(array $data, $no_photos = false)
$text .= " title='".$data["title"]."'";
}
if (!empty($data["images"])) {
// Only embedd a picture link when it seems to be a valid picture ("width" is set)
if (!empty($data["images"]) && !empty($data["images"][0]["width"])) {
$preview = str_replace(["[", "]"], ["&#91;", "&#93;"], htmlentities($data["images"][0]["src"], ENT_QUOTES, 'UTF-8', false));
// if the preview picture is larger than 500 pixels then show it in a larger mode
// But only, if the picture isn't higher than large (To prevent huge posts)
@ -315,9 +316,9 @@ function subscribe_to_hub($url, array $importer, array $contact, $hubmode = 'sub
DBA::update('contact', ['hub-verify' => $verify_token], ['id' => $contact['id']]);
}
Network::post($url, $params);
$postResult = Network::post($url, $params);
logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
logger('subscribe_to_hub: returns: ' . $postResult->getReturnCode(), LOGGER_DEBUG);
return;
@ -348,12 +349,12 @@ function drop_item($id)
// locate item to be deleted
$fields = ['id', 'uid', 'contact-id', 'deleted'];
$fields = ['id', 'uid', 'guid', 'contact-id', 'deleted'];
$item = Item::selectFirstForUser(local_user(), $fields, ['id' => $id]);
if (!DBA::isResult($item)) {
notice(L10n::t('Item not found.') . EOL);
goaway(System::baseUrl() . '/' . $_SESSION['return_url']);
goaway('/network');
}
if ($item['deleted']) {
@ -400,17 +401,17 @@ function drop_item($id)
}
// Now check how the user responded to the confirmation query
if (!empty($_REQUEST['canceled'])) {
goaway(System::baseUrl() . '/' . $_SESSION['return_url']);
goaway('/display/' . $item['guid']);
}
// delete the item
Item::deleteForUser(['id' => $item['id']], local_user());
goaway(System::baseUrl() . '/' . $_SESSION['return_url']);
goaway('/network');
//NOTREACHED
} else {
notice(L10n::t('Permission denied.') . EOL);
goaway(System::baseUrl() . '/' . $_SESSION['return_url']);
goaway('/display/' . $item['guid']);
//NOTREACHED
}
}

View file

@ -42,7 +42,7 @@ function replace_macros($s, $r) {
// pass $baseurl to all templates
$r['$baseurl'] = System::baseUrl();
$t = $a->template_engine();
$t = $a->getTemplateEngine();
try {
$output = $t->replaceMacros($s, $r);
} catch (Exception $e) {
@ -50,7 +50,7 @@ function replace_macros($s, $r) {
killme();
}
$a->save_timestamp($stamp1, "rendering");
$a->saveTimestamp($stamp1, "rendering");
return $output;
}
@ -473,7 +473,7 @@ function get_markup_template($s, $root = '') {
$stamp1 = microtime(true);
$a = get_app();
$t = $a->template_engine();
$t = $a->getTemplateEngine();
try {
$template = $t->getTemplateFile($s, $root);
} catch (Exception $e) {
@ -481,7 +481,7 @@ function get_markup_template($s, $root = '') {
killme();
}
$a->save_timestamp($stamp1, "file");
$a->saveTimestamp($stamp1, "file");
return $template;
}
@ -574,7 +574,7 @@ function logger($msg, $level = LOGGER_INFO) {
$stamp1 = microtime(true);
@file_put_contents($logfile, $logline, FILE_APPEND);
$a->save_timestamp($stamp1, "file");
$a->saveTimestamp($stamp1, "file");
}
/**
@ -634,7 +634,7 @@ function dlogger($msg, $level = LOGGER_INFO) {
$stamp1 = microtime(true);
@file_put_contents($logfile, $logline, FILE_APPEND);
$a->save_timestamp($stamp1, "file");
$a->saveTimestamp($stamp1, "file");
}
@ -1191,9 +1191,6 @@ function prepare_body(array &$item, $attach = false, $is_preview = false)
$a->page['htmlhead'] .= replace_macros(get_markup_template('videos_head.tpl'), [
'$baseurl' => System::baseUrl(),
]);
$a->page['end'] .= replace_macros(get_markup_template('videos_end.tpl'), [
'$baseurl' => System::baseUrl(),
]);
}
$url_parts = explode('/', $the_url);
@ -1417,7 +1414,7 @@ function get_plink($item) {
];
if (x($item, 'plink')) {
$ret["href"] = $a->remove_baseurl($item['plink']);
$ret["href"] = $a->removeBaseURL($item['plink']);
$ret["title"] = L10n::t('link to source');
}
@ -1911,58 +1908,3 @@ function format_network_name($network, $url = 0) {
return $network_name;
}
}
/**
* @brief Syntax based code highlighting for popular languages.
* @param string $s Code block
* @param string $lang Programming language
* @return string Formated html
*/
function text_highlight($s, $lang) {
if ($lang === 'js') {
$lang = 'javascript';
}
if ($lang === 'bash') {
$lang = 'sh';
}
// @TODO: Replace Text_Highlighter_Renderer_Html by scrivo/highlight.php
// Autoload the library to make constants available
class_exists('Text_Highlighter_Renderer_Html');
$options = [
'numbers' => HL_NUMBERS_LI,
'tabsize' => 4,
];
$tag_added = false;
$s = trim(html_entity_decode($s, ENT_COMPAT));
$s = str_replace(' ', "\t", $s);
/*
* The highlighter library insists on an opening php tag for php code blocks. If
* it isn't present, nothing is highlighted. So we're going to see if it's present.
* If not, we'll add it, and then quietly remove it after we get the processed output back.
*/
if ($lang === 'php' && strpos($s, '<?php') !== 0) {
$s = '<?php' . "\n" . $s;
$tag_added = true;
}
$renderer = new Text_Highlighter_Renderer_Html($options);
$factory = new Text_Highlighter();
$hl = $factory->factory($lang);
$hl->setRenderer($renderer);
$o = $hl->highlight($s);
$o = str_replace("\n", '', $o);
if ($tag_added) {
$b = substr($o, 0, strpos($o, '<li>'));
$e = substr($o, strpos($o, '</li>'));
$o = $b . $e;
}
return '<code>' . $o . '</code>';
}

114
index.php
View file

@ -23,11 +23,9 @@ use Friendica\Module\Login;
require_once 'boot.php';
$a = new App(__DIR__);
// We assume that the index.php is called by a frontend process
// The value is set to "true" by default in boot.php
$a->backend = false;
$a = new App(__DIR__, false);
/**
* Try to open the database;
@ -36,7 +34,7 @@ $a->backend = false;
require_once "include/dba.php";
// Missing DB connection: ERROR
if ($a->mode & App::MODE_LOCALCONFIGPRESENT && !($a->mode & App::MODE_DBAVAILABLE)) {
if ($a->getMode()->has(App\Mode::LOCALCONFIGPRESENT) && !$a->getMode()->has(App\Mode::DBAVAILABLE)) {
System::httpExit(500, ['title' => 'Error 500 - Internal Server Error', 'description' => 'Apologies but the website is unavailable at the moment.']);
}
@ -48,8 +46,12 @@ if ($a->isMaxProcessesReached() || $a->isMaxLoadReached()) {
System::httpExit(503, ['title' => 'Error 503 - Service Temporarily Unavailable', 'description' => 'System is currently overloaded. Please try again later.']);
}
if (!$a->isInstallMode()) {
if (Config::get('system', 'force_ssl') && ($a->get_scheme() == "http")
if (strstr($a->query_string, '.well-known/host-meta') && ($a->query_string != '.well-known/host-meta')) {
System::httpExit(404);
}
if (!$a->getMode()->isInstall()) {
if (Config::get('system', 'force_ssl') && ($a->getScheme() == "http")
&& (intval(Config::get('system', 'ssl_policy')) == SSL_POLICY_FULL)
&& (substr(System::baseUrl(), 0, 8) == "https://")
&& ($_SERVER['REQUEST_METHOD'] == 'GET')) {
@ -78,10 +80,10 @@ L10n::loadTranslationTable($lang);
*/
// Exclude the backend processes from the session management
if (!$a->is_backend()) {
if (!$a->isBackend()) {
$stamp1 = microtime(true);
session_start();
$a->save_timestamp($stamp1, "parser");
$a->saveTimestamp($stamp1, "parser");
} else {
$_SESSION = [];
Worker::executeIfIdle();
@ -91,7 +93,7 @@ if (!$a->is_backend()) {
* Language was set earlier, but we can over-ride it in the session.
* We have to do it here because the session was just now opened.
*/
if (x($_SESSION, 'authenticated') && !x($_SESSION, 'language')) {
if (!empty($_SESSION['authenticated']) && empty($_SESSION['language'])) {
$_SESSION['language'] = $lang;
// we haven't loaded user data yet, but we need user language
if (!empty($_SESSION['uid'])) {
@ -102,12 +104,12 @@ if (x($_SESSION, 'authenticated') && !x($_SESSION, 'language')) {
}
}
if (x($_SESSION, 'language') && ($_SESSION['language'] !== $lang)) {
if (!empty($_SESSION['language']) && $_SESSION['language'] !== $lang) {
$lang = $_SESSION['language'];
L10n::loadTranslationTable($lang);
}
if (!empty($_GET['zrl']) && $a->mode == App::MODE_NORMAL) {
if (!empty($_GET['zrl']) && $a->getMode()->isNormal()) {
$a->query_string = Profile::stripZrls($a->query_string);
if (!local_user()) {
// Only continue when the given profile link seems valid
@ -125,12 +127,12 @@ if (!empty($_GET['zrl']) && $a->mode == App::MODE_NORMAL) {
logger("Invalid ZRL parameter " . $_GET['zrl'], LOGGER_DEBUG);
header('HTTP/1.1 403 Forbidden');
echo "<h1>403 Forbidden</h1>";
killme();
exit();
}
}
}
if ((x($_GET,'owt')) && $a->mode == App::MODE_NORMAL) {
if (!empty($_GET['owt']) && $a->getMode()->isNormal()) {
$token = $_GET['owt'];
$a->query_string = Profile::stripQueryParam($a->query_string, 'owt');
Profile::openWebAuthInit($token);
@ -149,14 +151,10 @@ if ((x($_GET,'owt')) && $a->mode == App::MODE_NORMAL) {
Login::sessionAuth();
if (! x($_SESSION, 'authenticated')) {
if (empty($_SESSION['authenticated'])) {
header('X-Account-Management-Status: none');
}
/* set up page['htmlhead'] and page['end'] for the modules to use */
$a->page['htmlhead'] = '';
$a->page['end'] = '';
$_SESSION['sysmsg'] = defaults($_SESSION, 'sysmsg' , []);
$_SESSION['sysmsg_info'] = defaults($_SESSION, 'sysmsg_info' , []);
$_SESSION['last_updated'] = defaults($_SESSION, 'last_updated', []);
@ -169,14 +167,14 @@ $_SESSION['last_updated'] = defaults($_SESSION, 'last_updated', []);
// in install mode, any url loads install module
// but we need "view" module for stylesheet
if ($a->isInstallMode() && $a->module!="view") {
if ($a->getMode()->isInstall() && $a->module != 'view') {
$a->module = 'install';
} elseif (!($a->mode & App::MODE_MAINTENANCEDISABLED) && $a->module != "view") {
} elseif (!$a->getMode()->has(App\Mode::MAINTENANCEDISABLED) && $a->module != 'view') {
$a->module = 'maintenance';
} else {
check_url($a);
check_db(false);
check_addons($a);
Addon::check();
}
Nav::setSelected('nothing');
@ -295,11 +293,11 @@ if (strlen($a->module)) {
if (! $a->module_loaded) {
// Stupid browser tried to pre-fetch our Javascript img template. Don't log the event or return anything - just quietly exit.
if ((x($_SERVER, 'QUERY_STRING')) && preg_match('/{[0-9]}/', $_SERVER['QUERY_STRING']) !== 0) {
if (!empty($_SERVER['QUERY_STRING']) && preg_match('/{[0-9]}/', $_SERVER['QUERY_STRING']) !== 0) {
killme();
}
if ((x($_SERVER, 'QUERY_STRING')) && ($_SERVER['QUERY_STRING'] === 'q=internal_error.html') && isset($dreamhost_error_hack)) {
if (!empty($_SERVER['QUERY_STRING']) && ($_SERVER['QUERY_STRING'] === 'q=internal_error.html') && isset($dreamhost_error_hack)) {
logger('index.php: dreamhost_error_hack invoked. Original URI =' . $_SERVER['REQUEST_URI']);
goaway(System::baseUrl() . $_SERVER['REQUEST_URI']);
}
@ -307,11 +305,9 @@ if (strlen($a->module)) {
logger('index.php: page not found: ' . $_SERVER['REQUEST_URI'] . ' ADDRESS: ' . $_SERVER['REMOTE_ADDR'] . ' QUERY: ' . $_SERVER['QUERY_STRING'], LOGGER_DEBUG);
header($_SERVER["SERVER_PROTOCOL"] . ' 404 ' . L10n::t('Not Found'));
$tpl = get_markup_template("404.tpl");
$a->page['content'] = replace_macros(
$tpl,
[
'$message' => L10n::t('Page not found.')]
);
$a->page['content'] = replace_macros($tpl, [
'$message' => L10n::t('Page not found.')
]);
}
}
@ -326,11 +322,7 @@ if (file_exists($theme_info_file)) {
/* initialise content region */
if (! x($a->page, 'content')) {
$a->page['content'] = '';
}
if ($a->mode == App::MODE_NORMAL) {
if ($a->getMode()->isNormal()) {
Addon::callHooks('page_content_top', $a->page['content']);
}
@ -342,15 +334,21 @@ if ($a->module_loaded) {
$a->page['page_title'] = $a->module;
$placeholder = '';
Addon::callHooks($a->module . '_mod_init', $placeholder);
if ($a->module_class) {
Addon::callHooks($a->module . '_mod_init', $placeholder);
call_user_func([$a->module_class, 'init']);
} else if (function_exists($a->module . '_init')) {
Addon::callHooks($a->module . '_mod_init', $placeholder);
$func = $a->module . '_init';
$func($a);
}
// "rawContent" is especially meant for technical endpoints.
// This endpoint doesn't need any theme initialization or other comparable stuff.
if (!$a->error && $a->module_class) {
call_user_func([$a->module_class, 'rawContent']);
}
if (function_exists(str_replace('-', '_', $a->getCurrentTheme()) . '_init')) {
$func = str_replace('-', '_', $a->getCurrentTheme()) . '_init';
$func($a);
@ -405,24 +403,13 @@ if ($a->module_loaded) {
* theme choices made by the modules can take effect.
*/
$a->init_pagehead();
$a->initHead();
/*
* Build the page ending -- this is stuff that goes right before
* the closing </body> tag
*/
$a->init_page_end();
// If you're just visiting, let javascript take you home
if (x($_SESSION, 'visitor_home')) {
$homebase = $_SESSION['visitor_home'];
} elseif (local_user()) {
$homebase = 'profile/' . $a->user['nickname'];
}
if (isset($homebase)) {
$a->page['content'] .= '<script>var homebase="' . $homebase . '" ; </script>';
}
$a->initFooter();
/*
* now that we've been through the module content, see if the page reported
@ -444,36 +431,9 @@ if ($a->module != 'install' && $a->module != 'maintenance') {
Nav::build($a);
}
/*
* Add a "toggle mobile" link if we're using a mobile device
*/
if ($a->is_mobile || $a->is_tablet) {
if (isset($_SESSION['show-mobile']) && !$_SESSION['show-mobile']) {
$link = 'toggle_mobile?address=' . curPageURL();
} else {
$link = 'toggle_mobile?off=1&address=' . curPageURL();
}
$a->page['footer'] = replace_macros(
get_markup_template("toggle_mobile_footer.tpl"),
[
'$toggle_link' => $link,
'$toggle_text' => L10n::t('toggle mobile')]
);
}
/**
* Build the page - now that we have all the components
*/
if (!$a->theme['stylesheet']) {
$stylesheet = $a->getCurrentThemeStylesheetPath();
} else {
$stylesheet = $a->theme['stylesheet'];
}
$a->page['htmlhead'] = str_replace('{{$stylesheet}}', $stylesheet, $a->page['htmlhead']);
//$a->page['htmlhead'] = replace_macros($a->page['htmlhead'], array('$stylesheet' => $stylesheet));
if (isset($_GET["mode"]) && (($_GET["mode"] == "raw") || ($_GET["mode"] == "minimal"))) {
$doc = new DOMDocument();
@ -502,7 +462,7 @@ if (isset($_GET["mode"]) && ($_GET["mode"] == "raw")) {
echo substr($target->saveHTML(), 6, -8);
killme();
exit();
}
$page = $a->page;
@ -540,5 +500,3 @@ if (empty($template)) {
/// @TODO Looks unsafe (remote-inclusion), is maybe not but Theme::getPathForFile() uses file_exists() but does not escape anything
require_once $template;
killme();

View file

@ -1,16 +0,0 @@
<?php
use Friendica\App;
use Friendica\Network\Probe;
function acctlink_init()
{
if (x($_GET, 'addr')) {
$addr = trim($_GET['addr']);
$res = Probe::uri($addr);
if ($res['url']) {
goaway($res['url']);
killme();
}
}
}

View file

@ -83,8 +83,9 @@ function acl_content(App $a)
WHERE `uid` = %d AND NOT `self`
AND NOT `blocked` AND NOT `pending` AND NOT `archive`
AND `success_update` >= `failure_update`
AND `network` IN ('%s', '%s') $sql_extra2",
AND `network` IN ('%s', '%s', '%s') $sql_extra2",
intval(local_user()),
DBA::escape(Protocol::ACTIVITYPUB),
DBA::escape(Protocol::DFRN),
DBA::escape(Protocol::DIASPORA)
);
@ -169,10 +170,11 @@ function acl_content(App $a)
} elseif ($type == 'm') {
$r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag`, `addr` FROM `contact`
WHERE `uid` = %d AND NOT `self` AND NOT `blocked` AND NOT `pending` AND NOT `archive`
AND `success_update` >= `failure_update` AND `network` IN ('%s', '%s')
AND `success_update` >= `failure_update` AND `network` IN ('%s', '%s', '%s')
$sql_extra2
ORDER BY `name` ASC ",
intval(local_user()),
DBA::escape(Protocol::ACTIVITYPUB),
DBA::escape(Protocol::DFRN),
DBA::escape(Protocol::DIASPORA)
);

View file

@ -18,13 +18,14 @@ use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use Friendica\Model\Contact;
use Friendica\Model\Item;
use Friendica\Model\Register;
use Friendica\Model\User;
use Friendica\Module\Login;
use Friendica\Module\Tos;
use Friendica\Util\Arrays;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Temporal;
use Friendica\Util\Network;
use Friendica\Util\Temporal;
require_once 'include/enotify.php';
require_once 'include/text.php';
@ -33,11 +34,11 @@ require_once 'include/items.php';
/**
* @brief Process send data from the admin panels subpages
*
* This function acts as relais for processing the data send from the subpages
* This function acts as relay for processing the data send from the subpages
* of the admin panel. Depending on the 1st parameter of the url (argv[1])
* specialized functions are called to process the data from the subpages.
*
* The function itself does not return anything, but the subsequencely function
* The function itself does not return anything, but the subsequently function
* return the HTML for the pages of the admin panel.
*
* @param App $a
@ -77,7 +78,7 @@ function admin_post(App $a)
break;
case 'themes':
if ($a->argc < 2) {
if (is_ajax()) {
if ($a->isAjax()) {
return;
}
goaway('admin/');
@ -107,7 +108,7 @@ function admin_post(App $a)
}
info(L10n::t('Theme settings updated.'));
if (is_ajax()) {
if ($a->isAjax()) {
return;
}
$return_path = 'admin/themes/' . $theme;
@ -286,7 +287,7 @@ function admin_content(App $a)
$o = admin_page_summary($a);
}
if (is_ajax()) {
if ($a->isAjax()) {
echo $o;
killme();
return '';
@ -475,8 +476,8 @@ function admin_page_contactblock(App $a)
$total = DBA::count('contact', $condition);
$a->set_pager_total($total);
$a->set_pager_itemspage(30);
$a->setPagerTotal($total);
$a->setPagerItemsPage(30);
$statement = DBA::select('contact', [], $condition, ['limit' => [$a->pager['start'], $a->pager['itemspage']]]);
@ -866,15 +867,15 @@ function admin_page_summary(App $a)
// Legacy config file warning
if (file_exists('.htconfig.php')) {
$showwarning = true;
$warningtext[] = L10n::t('Friendica\'s configuration now is stored in config/local.ini.php, please copy config/local-sample.ini.php and move your config from <code>.htconfig.php</code>. See <a href="%s">the Config help page</a> for help with the transition.', $a->get_baseurl() . '/help/Config');
$warningtext[] = L10n::t('Friendica\'s configuration now is stored in config/local.ini.php, please copy config/local-sample.ini.php and move your config from <code>.htconfig.php</code>. See <a href="%s">the Config help page</a> for help with the transition.', $a->getBaseURL() . '/help/Config');
}
// Check server vitality
if (!admin_page_server_vital()) {
$showwarning = true;
$well_known = $a->get_baseurl() . '/.well-known/host-meta';
$well_known = $a->getBaseURL() . '/.well-known/host-meta';
$warningtext[] = L10n::t('<a href="%s">%s</a> is not reachable on your system. This is a severe configuration issue that prevents server to server communication. See <a href="%s">the installation page</a> for help.',
$well_known, $well_known, $a->get_baseurl() . '/help/Install');
$well_known, $well_known, $a->getBaseURL() . '/help/Install');
}
$r = q("SELECT `page-flags`, COUNT(`uid`) AS `count` FROM `user` GROUP BY `page-flags`");
@ -895,8 +896,7 @@ function admin_page_summary(App $a)
logger('accounts: ' . print_r($accounts, true), LOGGER_DATA);
$r = q("SELECT COUNT(`id`) AS `count` FROM `register`");
$pending = $r[0]['count'];
$pending = Register::getPendingCount();
$r = q("SELECT COUNT(*) AS `total` FROM `queue` WHERE 1");
$queue = (($r) ? $r[0]['total'] : 0);
@ -909,6 +909,15 @@ function admin_page_summary(App $a)
$queues = ['label' => L10n::t('Message queues'), 'queue' => $queue, 'workerq' => $workerqueue];
$r = q("SHOW variables LIKE 'max_allowed_packet'");
$max_allowed_packet = (($r) ? $r[0]['Value'] : 0);
$server_settings = ['label' => L10n::t('Server Settings'),
'php' => ['upload_max_filesize' => ini_get('upload_max_filesize'),
'post_max_size' => ini_get('post_max_size'),
'memory_limit' => ini_get('memory_limit')],
'mysql' => ['max_allowed_packet' => $max_allowed_packet]];
$t = get_markup_template('admin/summary.tpl');
return replace_macros($t, [
'$title' => L10n::t('Administration'),
@ -923,6 +932,7 @@ function admin_page_summary(App $a)
'$codename' => FRIENDICA_CODENAME,
'$build' => Config::get('system', 'build'),
'$addons' => [L10n::t('Active addons'), $a->addons],
'$serversettings' => $server_settings,
'$showwarning' => $showwarning,
'$warningtext' => $warningtext
]);
@ -1002,7 +1012,7 @@ function admin_page_site_post(App $a)
// update config
Config::set('system', 'hostname', parse_url($new_url, PHP_URL_HOST));
Config::set('system', 'url', $new_url);
$a->set_baseurl($new_url);
$a->setBaseURL($new_url);
// send relocate
$users = q("SELECT `uid` FROM `user` WHERE `account_removed` = 0 AND `account_expired` = 0");
@ -1114,7 +1124,7 @@ function admin_page_site_post(App $a)
Worker::add(PRIORITY_LOW, 'Directory');
}
if ($a->get_path() != "") {
if ($a->getURLPath() != "") {
$diaspora_enabled = false;
}
if ($ssl_policy != intval(Config::get('system', 'ssl_policy'))) {
@ -1251,7 +1261,7 @@ function admin_page_site_post(App $a)
Config::set('system', 'dbclean-expire-unclaimed', $dbclean_unclaimed);
if ($itemcache != '') {
$itemcache = App::realpath($itemcache);
$itemcache = App::getRealPath($itemcache);
}
Config::set('system', 'itemcache', $itemcache);
@ -1259,13 +1269,13 @@ function admin_page_site_post(App $a)
Config::set('system', 'max_comments', $max_comments);
if ($temppath != '') {
$temppath = App::realpath($temppath);
$temppath = App::getRealPath($temppath);
}
Config::set('system', 'temppath', $temppath);
if ($basepath != '') {
$basepath = App::realpath($basepath);
$basepath = App::getRealPath($basepath);
}
Config::set('system', 'basepath', $basepath);
@ -1409,9 +1419,9 @@ function admin_page_site(App $a)
];
if (empty(Config::get('config', 'hostname'))) {
Config::set('config', 'hostname', $a->get_hostname());
Config::set('config', 'hostname', $a->getHostName());
}
$diaspora_able = ($a->get_path() == "");
$diaspora_able = ($a->getURLPath() == "");
$optimize_max_tablesize = Config::get('system', 'optimize_max_tablesize', -1);
@ -1478,7 +1488,7 @@ function admin_page_site(App $a)
'$community_page_style' => ['community_page_style', L10n::t("Community pages for visitors"), Config::get('system','community_page_style'), L10n::t("Which community pages should be available for visitors. Local users always see both pages."), $community_page_style_choices],
'$max_author_posts_community_page' => ['max_author_posts_community_page', L10n::t("Posts per user on community page"), Config::get('system','max_author_posts_community_page'), L10n::t("The maximum number of posts per user on the community page. \x28Not valid for 'Global Community'\x29")],
'$ostatus_disabled' => ['ostatus_disabled', L10n::t("Enable OStatus support"), !Config::get('system','ostatus_disabled'), L10n::t("Provide built-in OStatus \x28StatusNet, GNU Social etc.\x29 compatibility. All communications in OStatus are public, so privacy warnings will be occasionally displayed.")],
'$ostatus_full_threads' => ['ostatus_full_threads', L10n::t("Only import OStatus threads from our contacts"), Config::get('system','ostatus_full_threads'), L10n::t("Normally we import every content from our OStatus contacts. With this option we only store threads that are started by a contact that is known on our system.")],
'$ostatus_full_threads' => ['ostatus_full_threads', L10n::t("Only import OStatus/ActivityPub threads from our contacts"), Config::get('system','ostatus_full_threads'), L10n::t("Normally we import every content from our OStatus and ActivityPub contacts. With this option we only store threads that are started by a contact that is known on our system.")],
'$ostatus_not_able' => L10n::t("OStatus support can only be enabled if threading is enabled."),
'$diaspora_able' => $diaspora_able,
'$diaspora_not_able' => L10n::t("Diaspora support can't be enabled because Friendica was installed into a sub directory."),
@ -1782,17 +1792,13 @@ function admin_page_users(App $a)
}
/* get pending */
$pending = q("SELECT `register`.*, `contact`.`name`, `user`.`email`
FROM `register`
INNER JOIN `contact` ON `register`.`uid` = `contact`.`uid`
INNER JOIN `user` ON `register`.`uid` = `user`.`uid`;");
$pending = Register::getPending();
/* get users */
$total = q("SELECT COUNT(*) AS `total` FROM `user` WHERE 1");
if (count($total)) {
$a->set_pager_total($total[0]['total']);
$a->set_pager_itemspage(100);
$a->setPagerTotal($total[0]['total']);
$a->setPagerItemsPage(100);
}
/* ordering */
@ -1912,7 +1918,7 @@ function admin_page_users(App $a)
'$h_users' => L10n::t('Users'),
'$h_newuser' => L10n::t('New User'),
'$th_deleted' => [L10n::t('Name'), L10n::t('Email'), L10n::t('Register date'), L10n::t('Last login'), L10n::t('Last item'), L10n::t('Deleted since')],
'$th_deleted' => [L10n::t('Name'), L10n::t('Email'), L10n::t('Register date'), L10n::t('Last login'), L10n::t('Last item'), L10n::t('Delete in')],
'$th_users' => $th_users,
'$order_users' => $order,
'$order_direction_users' => $order_direction,
@ -2526,7 +2532,7 @@ function admin_page_features_post(App $a)
*/
function admin_page_features(App $a)
{
if ((argc() > 1) && (argv(1) === 'features')) {
if (($a->argc > 1) && ($a->getArgumentValue(1) === 'features')) {
$arr = [];
$features = Feature::get(false);
@ -2557,6 +2563,5 @@ function admin_page_features(App $a)
function admin_page_server_vital()
{
// Fetch the host-meta to check if this really is a vital server
$serverret = Network::curl(System::baseUrl() . '/.well-known/host-meta');
return $serverret["success"];
return Network::curl(System::baseUrl() . '/.well-known/host-meta')->isSuccess();
}

View file

@ -8,13 +8,12 @@ use Friendica\Content\ContactSelector;
use Friendica\Core\L10n;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Model\GContact;
use Friendica\Model\Profile;
use Friendica\Model;
use Friendica\Module;
use Friendica\Util\Proxy as ProxyUtils;
require_once 'include/dba.php';
require_once 'mod/contacts.php';
function allfriends_content(App $a)
{
@ -42,13 +41,13 @@ function allfriends_content(App $a)
}
$a->page['aside'] = "";
Profile::load($a, "", 0, Contact::getDetailsByURL($contact["url"]));
Model\Profile::load($a, "", 0, Model\Contact::getDetailsByURL($contact["url"]));
$total = GContact::countAllFriends(local_user(), $cid);
$total = Model\GContact::countAllFriends(local_user(), $cid);
$a->set_pager_total($total);
$a->setPagerTotal($total);
$r = GContact::allFriends(local_user(), $cid, $a->pager['start'], $a->pager['itemspage']);
$r = Model\GContact::allFriends(local_user(), $cid, $a->pager['start'], $a->pager['itemspage']);
if (!DBA::isResult($r)) {
$o .= L10n::t('No friends to display.');
return $o;
@ -59,7 +58,7 @@ function allfriends_content(App $a)
$entries = [];
foreach ($r as $rr) {
//get further details of the contact
$contact_details = Contact::getDetailsByURL($rr['url'], $uid, $rr);
$contact_details = Model\Contact::getDetailsByURL($rr['url'], $uid, $rr);
$photo_menu = '';
@ -68,11 +67,11 @@ function allfriends_content(App $a)
// If the contact is not common to the user, Connect/Follow' will be added to the photo menu
if ($rr['cid']) {
$rr['id'] = $rr['cid'];
$photo_menu = Contact::photoMenu($rr);
$photo_menu = Model\Contact::photoMenu($rr);
} else {
$connlnk = System::baseUrl() . '/follow/?url=' . $rr['url'];
$photo_menu = [
'profile' => [L10n::t("View Profile"), Contact::magicLink($rr['url'])],
'profile' => [L10n::t("View Profile"), Model\Contact::magicLink($rr['url'])],
'follow' => [L10n::t("Connect/Follow"), $connlnk]
];
}
@ -86,7 +85,7 @@ function allfriends_content(App $a)
'details' => $contact_details['location'],
'tags' => $contact_details['keywords'],
'about' => $contact_details['about'],
'account_type' => Contact::getAccountType($contact_details),
'account_type' => Model\Contact::getAccountType($contact_details),
'network' => ContactSelector::networkToName($contact_details['network'], $contact_details['url']),
'photo_menu' => $photo_menu,
'conntxt' => L10n::t('Connect'),
@ -96,7 +95,7 @@ function allfriends_content(App $a)
$entries[] = $entry;
}
$tab_str = contacts_tab($a, $contact, 4);
$tab_str = Module\Contact::getTabsHTML($a, $contact, 4);
$tpl = get_markup_template('viewcontact_template.tpl');

View file

@ -6,9 +6,11 @@
use Friendica\Content\Text;
use Friendica\Core\L10n;
function visible_lf($s)
function visible_whitespace($s)
{
return str_replace("\n", '<br />', $s);
$s = str_replace(' ', '&nbsp;', $s);
return str_replace(["\r\n", "\n", "\r"], '<br />', $s);
}
function babel_content()
@ -20,19 +22,19 @@ function babel_content()
$bbcode = trim($_REQUEST['text']);
$results[] = [
'title' => L10n::t('Source input'),
'content' => visible_lf($bbcode)
'content' => visible_whitespace($bbcode)
];
$plain = Text\BBCode::toPlaintext($bbcode, false);
$results[] = [
'title' => L10n::t('BBCode::toPlaintext'),
'content' => visible_lf($plain)
'content' => visible_whitespace($plain)
];
$html = Text\BBCode::convert($bbcode);
$results[] = [
'title' => L10n::t("BBCode::convert \x28raw HTML\x29"),
'content' => htmlspecialchars($html)
'title' => L10n::t('BBCode::convert (raw HTML)'),
'content' => visible_whitespace(htmlspecialchars($html))
];
$results[] = [
@ -43,13 +45,13 @@ function babel_content()
$bbcode2 = Text\HTML::toBBCode($html);
$results[] = [
'title' => L10n::t('BBCode::convert => HTML::toBBCode'),
'content' => visible_lf($bbcode2)
'content' => visible_whitespace($bbcode2)
];
$markdown = Text\BBCode::toMarkdown($bbcode);
$results[] = [
'title' => L10n::t('BBCode::toMarkdown'),
'content' => visible_lf($markdown)
'content' => visible_whitespace($markdown)
];
$html2 = Text\Markdown::convert($markdown);
@ -61,22 +63,33 @@ function babel_content()
$bbcode3 = Text\Markdown::toBBCode($markdown);
$results[] = [
'title' => L10n::t('BBCode::toMarkdown => Markdown::toBBCode'),
'content' => visible_lf($bbcode3)
'content' => visible_whitespace($bbcode3)
];
$bbcode4 = Text\HTML::toBBCode($html2);
$results[] = [
'title' => L10n::t('BBCode::toMarkdown => Markdown::convert => HTML::toBBCode'),
'content' => visible_lf($bbcode4)
'content' => visible_whitespace($bbcode4)
];
break;
case 'markdown':
$markdown = trim($_REQUEST['text']);
$results[] = [
'title' => L10n::t('Source input \x28Diaspora format\x29'),
'title' => L10n::t('Source input (Diaspora format)'),
'content' => '<pre>' . $markdown . '</pre>'
];
$html = Text\Markdown::convert($markdown);
$results[] = [
'title' => L10n::t('Markdown::convert (raw HTML)'),
'content' => htmlspecialchars($html)
];
$results[] = [
'title' => L10n::t('Markdown::convert'),
'content' => $html
];
$bbcode = Text\Markdown::toBBCode($markdown);
$results[] = [
'title' => L10n::t('Markdown::toBBCode'),
@ -86,7 +99,7 @@ function babel_content()
case 'html' :
$html = trim($_REQUEST['text']);
$results[] = [
'title' => L10n::t("Raw HTML input"),
'title' => L10n::t('Raw HTML input'),
'content' => htmlspecialchars($html)
];
@ -98,7 +111,24 @@ function babel_content()
$bbcode = Text\HTML::toBBCode($html);
$results[] = [
'title' => L10n::t('HTML::toBBCode'),
'content' => visible_lf($bbcode)
'content' => visible_whitespace($bbcode)
];
$html2 = Text\BBCode::convert($bbcode);
$results[] = [
'title' => L10n::t('HTML::toBBCode => BBCode::convert'),
'content' => $html2
];
$results[] = [
'title' => L10n::t('HTML::toBBCode => BBCode::convert (raw HTML)'),
'content' => htmlspecialchars($html2)
];
$markdown = Text\HTML::toMarkdown($html);
$results[] = [
'title' => L10n::t('HTML::toMarkdown'),
'content' => visible_whitespace($markdown)
];
$text = Text\HTML::toPlaintext($html);
@ -111,7 +141,7 @@ function babel_content()
$tpl = get_markup_template('babel.tpl');
$o = replace_macros($tpl, [
'$text' => ['text', L10n::t('Source text'), defaults($_REQUEST, 'text', ''), ''],
'$text' => ['text', L10n::t('Source text'), htmlentities(defaults($_REQUEST, 'text', '')), ''],
'$type_bbcode' => ['type', L10n::t('BBCode'), 'bbcode', '', defaults($_REQUEST, 'type', 'bbcode') == 'bbcode'],
'$type_markdown' => ['type', L10n::t('Markdown'), 'markdown', '', defaults($_REQUEST, 'type', 'bbcode') == 'markdown'],
'$type_html' => ['type', L10n::t('HTML'), 'html', '', defaults($_REQUEST, 'type', 'bbcode') == 'html'],

View file

@ -30,6 +30,10 @@ function bookmarklet_content(App $a)
$page = normalise_link(System::baseUrl() . "/bookmarklet");
if (!strstr($referer, $page)) {
if (empty($_REQUEST["url"])) {
System::httpExit(400, ["title" => L10n::t('Bad Request')]);
}
$content = add_page_info($_REQUEST["url"]);
$x = [

View file

@ -94,11 +94,6 @@ function cal_content(App $a)
'$i18n' => $i18n,
]);
$etpl = get_markup_template('event_end.tpl');
$a->page['end'] .= replace_macros($etpl, [
'$baseurl' => System::baseUrl(),
]);
$mode = 'view';
$y = 0;
$m = 0;

View file

@ -7,13 +7,12 @@ use Friendica\App;
use Friendica\Content\ContactSelector;
use Friendica\Core\L10n;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Model\GContact;
use Friendica\Model\Profile;
use Friendica\Model;
use Friendica\Module;
use Friendica\Util\Proxy as ProxyUtils;
require_once 'include/dba.php';
require_once 'mod/contacts.php';
function common_content(App $a)
{
@ -42,16 +41,16 @@ function common_content(App $a)
if (DBA::isResult($contact)) {
$a->page['aside'] = "";
Profile::load($a, "", 0, Contact::getDetailsByURL($contact["url"]));
Model\Profile::load($a, "", 0, Model\Contact::getDetailsByURL($contact["url"]));
}
} else {
$contact = DBA::selectFirst('contact', ['name', 'url', 'photo', 'uid', 'id'], ['self' => true, 'uid' => $uid]);
if (DBA::isResult($contact)) {
$vcard_widget = replace_macros(get_markup_template("vcard-widget.tpl"), [
'$name' => htmlentities($contact['name']),
'$name' => htmlentities($contact['name']),
'$photo' => $contact['photo'],
'url' => 'contacts/' . $cid
'url' => 'contact/' . $cid
]);
if (!x($a->page, 'aside')) {
@ -65,12 +64,12 @@ function common_content(App $a)
return;
}
if (!$cid && Profile::getMyURL()) {
$contact = DBA::selectFirst('contact', ['id'], ['nurl' => normalise_link(Profile::getMyURL()), 'uid' => $uid]);
if (!$cid && Model\Profile::getMyURL()) {
$contact = DBA::selectFirst('contact', ['id'], ['nurl' => normalise_link(Model\Profile::getMyURL()), 'uid' => $uid]);
if (DBA::isResult($contact)) {
$cid = $contact['id'];
} else {
$gcontact = DBA::selectFirst('gcontact', ['id'], ['nurl' => normalise_link(Profile::getMyURL())]);
$gcontact = DBA::selectFirst('gcontact', ['id'], ['nurl' => normalise_link(Model\Profile::getMyURL())]);
if (DBA::isResult($gcontact)) {
$zcid = $gcontact['id'];
}
@ -82,22 +81,22 @@ function common_content(App $a)
}
if ($cid) {
$t = GContact::countCommonFriends($uid, $cid);
$t = Model\GContact::countCommonFriends($uid, $cid);
} else {
$t = GContact::countCommonFriendsZcid($uid, $zcid);
$t = Model\GContact::countCommonFriendsZcid($uid, $zcid);
}
if ($t > 0) {
$a->set_pager_total($t);
$a->setPagerTotal($t);
} else {
notice(L10n::t('No contacts in common.') . EOL);
return $o;
}
if ($cid) {
$r = GContact::commonFriends($uid, $cid, $a->pager['start'], $a->pager['itemspage']);
$r = Model\GContact::commonFriends($uid, $cid, $a->pager['start'], $a->pager['itemspage']);
} else {
$r = GContact::commonFriendsZcid($uid, $zcid, $a->pager['start'], $a->pager['itemspage']);
$r = Model\GContact::commonFriendsZcid($uid, $zcid, $a->pager['start'], $a->pager['itemspage']);
}
if (!DBA::isResult($r)) {
@ -109,13 +108,13 @@ function common_content(App $a)
$entries = [];
foreach ($r as $rr) {
//get further details of the contact
$contact_details = Contact::getDetailsByURL($rr['url'], $uid);
$contact_details = Model\Contact::getDetailsByURL($rr['url'], $uid);
// $rr['id'] is needed to use contact_photo_menu()
/// @TODO Adding '/" here avoids E_NOTICE on missing constants
$rr['id'] = $rr['cid'];
$photo_menu = Contact::photoMenu($rr);
$photo_menu = Model\Contact::photoMenu($rr);
$entry = [
'url' => $rr['url'],
@ -126,7 +125,7 @@ function common_content(App $a)
'details' => $contact_details['location'],
'tags' => $contact_details['keywords'],
'about' => $contact_details['about'],
'account_type' => Contact::getAccountType($contact_details),
'account_type' => Model\Contact::getAccountType($contact_details),
'network' => ContactSelector::networkToName($contact_details['network'], $contact_details['url']),
'photo_menu' => $photo_menu,
'id' => ++$id,
@ -137,7 +136,7 @@ function common_content(App $a)
$title = '';
$tab_str = '';
if ($cmd === 'loc' && $cid && local_user() == $uid) {
$tab_str = contacts_tab($a, $contact, 4);
$tab_str = Module\Contact::getTabsHTML($a, $contact, 4);
} else {
$title = L10n::t('Common Friends');
}

View file

@ -154,7 +154,7 @@ function community_content(App $a, $update = 0)
$itemspage_network = $a->force_max_items;
}
$a->set_pager_itemspage($itemspage_network);
$a->setPagerItemsPage($itemspage_network);
$r = community_getitems($a->pager['start'], $a->pager['itemspage'], $content, $accounttype);

View file

@ -1,1139 +0,0 @@
<?php
/**
* @file mod/contacts.php
*/
use Friendica\App;
use Friendica\Content\ContactSelector;
use Friendica\Content\Nav;
use Friendica\Content\Text\BBCode;
use Friendica\Content\Widget;
use Friendica\Core\Addon;
use Friendica\Core\L10n;
use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Model\GContact;
use Friendica\Model\Group;
use Friendica\Model\Profile;
use Friendica\Network\Probe;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Proxy as ProxyUtils;
use Friendica\Core\ACL;
function contacts_init(App $a)
{
if (!local_user()) {
return;
}
$nets = defaults($_GET, 'nets', '');
if ($nets == "all") {
$nets = "";
}
if (!x($a->page, 'aside')) {
$a->page['aside'] = '';
}
$contact_id = null;
$contact = null;
if ((($a->argc == 2) && intval($a->argv[1])) || (($a->argc == 3) && intval($a->argv[1]) && in_array($a->argv[2], ['posts', 'conversations']))) {
$contact_id = intval($a->argv[1]);
$contact = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => local_user()]);
if (!DBA::isResult($contact)) {
$contact = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => 0]);
}
}
if (DBA::isResult($contact)) {
if ($contact['self']) {
if (($a->argc == 3) && intval($a->argv[1]) && in_array($a->argv[2], ['posts', 'conversations'])) {
goaway('profile/' . $contact['nick']);
} else {
goaway('profile/' . $contact['nick'] . '?tab=profile');
}
}
$a->data['contact'] = $contact;
if (($a->data['contact']['network'] != "") && ($a->data['contact']['network'] != Protocol::DFRN)) {
$networkname = format_network_name($a->data['contact']['network'], $a->data['contact']['url']);
} else {
$networkname = '';
}
/// @TODO Add nice spaces
$vcard_widget = replace_macros(get_markup_template("vcard-widget.tpl"), [
'$name' => htmlentities($a->data['contact']['name']),
'$photo' => $a->data['contact']['photo'],
'$url' => Contact::MagicLink($a->data['contact']['url']),
'$addr' => (($a->data['contact']['addr'] != "") ? ($a->data['contact']['addr']) : ""),
'$network_name' => $networkname,
'$network' => L10n::t('Network:'),
'$account_type' => Contact::getAccountType($a->data['contact'])
]);
$findpeople_widget = '';
$follow_widget = '';
$networks_widget = '';
} else {
$vcard_widget = '';
$networks_widget = Widget::networks('contacts', $nets);
if (isset($_GET['add'])) {
$follow_widget = Widget::follow($_GET['add']);
} else {
$follow_widget = Widget::follow();
}
$findpeople_widget = Widget::findPeople();
}
if ($contact['uid'] != 0) {
$groups_widget = Group::sidebarWidget('contacts', 'group', 'full', 'everyone', $contact_id);
} else {
$groups_widget = null;
}
$a->page['aside'] .= replace_macros(get_markup_template("contacts-widget-sidebar.tpl"), [
'$vcard_widget' => $vcard_widget,
'$findpeople_widget' => $findpeople_widget,
'$follow_widget' => $follow_widget,
'$groups_widget' => $groups_widget,
'$networks_widget' => $networks_widget
]);
$base = System::baseUrl();
$tpl = get_markup_template("contacts-head.tpl");
$a->page['htmlhead'] .= replace_macros($tpl, [
'$baseurl' => System::baseUrl(true),
'$base' => $base
]);
$tpl = get_markup_template("contacts-end.tpl");
$a->page['end'] .= replace_macros($tpl, [
'$baseurl' => System::baseUrl(true),
'$base' => $base
]);
}
function contacts_batch_actions(App $a)
{
$contacts_id = $_POST['contact_batch'];
if (!is_array($contacts_id)) {
return;
}
$orig_records = q("SELECT * FROM `contact` WHERE `id` IN (%s) AND `uid` = %d AND `self` = 0",
implode(",", $contacts_id),
intval(local_user())
);
$count_actions = 0;
foreach ($orig_records as $orig_record) {
$contact_id = $orig_record['id'];
if (x($_POST, 'contacts_batch_update')) {
_contact_update($contact_id);
$count_actions++;
}
if (x($_POST, 'contacts_batch_block')) {
_contact_block($contact_id);
$count_actions++;
}
if (x($_POST, 'contacts_batch_ignore')) {
_contact_ignore($contact_id);
$count_actions++;
}
if (x($_POST, 'contacts_batch_archive')) {
$r = _contact_archive($contact_id, $orig_record);
if ($r) {
$count_actions++;
}
}
if (x($_POST, 'contacts_batch_drop')) {
_contact_drop($orig_record);
$count_actions++;
}
}
if ($count_actions > 0) {
info(L10n::tt("%d contact edited.", "%d contacts edited.", $count_actions));
}
if (x($_SESSION, 'return_url')) {
goaway('' . $_SESSION['return_url']);
} else {
goaway('contacts');
}
}
function contacts_post(App $a)
{
if (!local_user()) {
return;
}
if ($a->argv[1] === "batch") {
contacts_batch_actions($a);
return;
}
$contact_id = intval($a->argv[1]);
if (!$contact_id) {
return;
}
if (!DBA::exists('contact', ['id' => $contact_id, 'uid' => local_user()])) {
notice(L10n::t('Could not access contact record.') . EOL);
goaway('contacts');
return; // NOTREACHED
}
Addon::callHooks('contact_edit_post', $_POST);
$profile_id = intval(defaults($_POST, 'profile-assign', 0));
if ($profile_id) {
if (!DBA::exists('profile', ['id' => $profile_id, 'uid' => local_user()])) {
notice(L10n::t('Could not locate selected profile.') . EOL);
return;
}
}
$hidden = intval($_POST['hidden']);
$notify = intval($_POST['notify']);
$fetch_further_information = intval(defaults($_POST, 'fetch_further_information', 0));
$ffi_keyword_blacklist = escape_tags(trim(defaults($_POST, 'ffi_keyword_blacklist', '')));
$priority = intval(defaults($_POST, 'poll', 0));
if ($priority > 5 || $priority < 0) {
$priority = 0;
}
$info = escape_tags(trim($_POST['info']));
$r = q("UPDATE `contact` SET `profile-id` = %d, `priority` = %d , `info` = '%s',
`hidden` = %d, `notify_new_posts` = %d, `fetch_further_information` = %d,
`ffi_keyword_blacklist` = '%s' WHERE `id` = %d AND `uid` = %d",
intval($profile_id),
intval($priority),
DBA::escape($info),
intval($hidden),
intval($notify),
intval($fetch_further_information),
DBA::escape($ffi_keyword_blacklist),
intval($contact_id),
intval(local_user())
);
if (DBA::isResult($r)) {
info(L10n::t('Contact updated.') . EOL);
} else {
notice(L10n::t('Failed to update contact record.') . EOL);
}
$contact = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => local_user()]);
if (DBA::isResult($contact)) {
$a->data['contact'] = $contact;
}
return;
}
/* contact actions */
function _contact_update($contact_id)
{
$contact = DBA::selectFirst('contact', ['uid', 'url', 'network'], ['id' => $contact_id, 'uid' => local_user()]);
if (!DBA::isResult($contact)) {
return;
}
$uid = $contact["uid"];
if ($contact["network"] == Protocol::OSTATUS) {
$result = Contact::createFromProbe($uid, $contact["url"], false, $contact["network"]);
if ($result['success']) {
q("UPDATE `contact` SET `subhub` = 1 WHERE `id` = %d", intval($contact_id));
}
} else {
// pull feed and consume it, which should subscribe to the hub.
Worker::add(PRIORITY_HIGH, "OnePoll", $contact_id, "force");
}
}
function _contact_update_profile($contact_id)
{
$contact = DBA::selectFirst('contact', ['uid', 'url', 'network'], ['id' => $contact_id, 'uid' => local_user()]);
if (!DBA::isResult($contact)) {
return;
}
$uid = $contact["uid"];
$data = Probe::uri($contact["url"], "", 0, false);
// "Feed" or "Unknown" is mostly a sign of communication problems
if ((in_array($data["network"], [Protocol::FEED, Protocol::PHANTOM])) && ($data["network"] != $contact["network"])) {
return;
}
$updatefields = ["name", "nick", "url", "addr", "batch", "notify", "poll", "request", "confirm",
"poco", "network", "alias"];
$update = [];
if ($data["network"] == Protocol::OSTATUS) {
$result = Contact::createFromProbe($uid, $data["url"], false);
if ($result['success']) {
$update["subhub"] = true;
}
}
foreach ($updatefields AS $field) {
if (isset($data[$field]) && ($data[$field] != "")) {
$update[$field] = $data[$field];
}
}
$update["nurl"] = normalise_link($data["url"]);
$query = "";
if (isset($data["priority"]) && ($data["priority"] != 0)) {
$query = "`priority` = " . intval($data["priority"]);
}
foreach ($update AS $key => $value) {
if ($query != "") {
$query .= ", ";
}
$query .= "`" . $key . "` = '" . DBA::escape($value) . "'";
}
if ($query == "") {
return;
}
$r = q("UPDATE `contact` SET $query WHERE `id` = %d AND `uid` = %d",
intval($contact_id),
intval(local_user())
);
// Update the entry in the contact table
Contact::updateAvatar($data['photo'], local_user(), $contact_id, true);
// Update the entry in the gcontact table
GContact::updateFromProbe($data["url"]);
}
function _contact_block($contact_id)
{
$blocked = !Contact::isBlockedByUser($contact_id, local_user());
Contact::setBlockedForUser($contact_id, local_user(), $blocked);
}
function _contact_ignore($contact_id)
{
$ignored = !Contact::isIgnoredByUser($contact_id, local_user());
Contact::setIgnoredForUser($contact_id, local_user(), $ignored);
}
function _contact_archive($contact_id, $orig_record)
{
$archived = (($orig_record['archive']) ? 0 : 1);
$r = q("UPDATE `contact` SET `archive` = %d WHERE `id` = %d AND `uid` = %d",
intval($archived),
intval($contact_id),
intval(local_user())
);
return DBA::isResult($r);
}
function _contact_drop($orig_record)
{
$a = get_app();
$r = q("SELECT `contact`.*, `user`.* FROM `contact` INNER JOIN `user` ON `contact`.`uid` = `user`.`uid`
WHERE `user`.`uid` = %d AND `contact`.`self` LIMIT 1",
intval($a->user['uid'])
);
if (!DBA::isResult($r)) {
return;
}
Contact::terminateFriendship($r[0], $orig_record);
Contact::remove($orig_record['id']);
}
function contacts_content(App $a, $update = 0)
{
$sort_type = 0;
$o = '';
Nav::setSelected('contacts');
if (!local_user()) {
notice(L10n::t('Permission denied.') . EOL);
return;
}
if ($a->argc == 3) {
$contact_id = intval($a->argv[1]);
if (!$contact_id) {
return;
}
$cmd = $a->argv[2];
$orig_record = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => [0, local_user()], 'self' => false]);
if (!DBA::isResult($orig_record)) {
notice(L10n::t('Could not access contact record.') . EOL);
goaway('contacts');
return; // NOTREACHED
}
if ($cmd === 'update' && ($orig_record['uid'] != 0)) {
_contact_update($contact_id);
goaway('contacts/' . $contact_id);
// NOTREACHED
}
if ($cmd === 'updateprofile' && ($orig_record['uid'] != 0)) {
_contact_update_profile($contact_id);
goaway('crepair/' . $contact_id);
// NOTREACHED
}
if ($cmd === 'block') {
_contact_block($contact_id);
$blocked = Contact::isBlockedByUser($contact_id, local_user());
info(($blocked ? L10n::t('Contact has been blocked') : L10n::t('Contact has been unblocked')) . EOL);
goaway('contacts/' . $contact_id);
return; // NOTREACHED
}
if ($cmd === 'ignore') {
_contact_ignore($contact_id);
$ignored = Contact::isIgnoredByUser($contact_id, local_user());
info(($ignored ? L10n::t('Contact has been ignored') : L10n::t('Contact has been unignored')) . EOL);
goaway('contacts/' . $contact_id);
return; // NOTREACHED
}
if ($cmd === 'archive' && ($orig_record['uid'] != 0)) {
$r = _contact_archive($contact_id, $orig_record);
if ($r) {
$archived = (($orig_record['archive']) ? 0 : 1);
info((($archived) ? L10n::t('Contact has been archived') : L10n::t('Contact has been unarchived')) . EOL);
}
goaway('contacts/' . $contact_id);
return; // NOTREACHED
}
if ($cmd === 'drop' && ($orig_record['uid'] != 0)) {
// Check if we should do HTML-based delete confirmation
if (x($_REQUEST, 'confirm')) {
// <form> can't take arguments in its "action" parameter
// so add any arguments as hidden inputs
$query = explode_querystring($a->query_string);
$inputs = [];
foreach ($query['args'] as $arg) {
if (strpos($arg, 'confirm=') === false) {
$arg_parts = explode('=', $arg);
$inputs[] = ['name' => $arg_parts[0], 'value' => $arg_parts[1]];
}
}
$a->page['aside'] = '';
return replace_macros(get_markup_template('contact_drop_confirm.tpl'), [
'$header' => L10n::t('Drop contact'),
'$contact' => _contact_detail_for_template($orig_record),
'$method' => 'get',
'$message' => L10n::t('Do you really want to delete this contact?'),
'$extra_inputs' => $inputs,
'$confirm' => L10n::t('Yes'),
'$confirm_url' => $query['base'],
'$confirm_name' => 'confirmed',
'$cancel' => L10n::t('Cancel'),
]);
}
// Now check how the user responded to the confirmation query
if (x($_REQUEST, 'canceled')) {
if (x($_SESSION, 'return_url')) {
goaway('' . $_SESSION['return_url']);
} else {
goaway('contacts');
}
}
_contact_drop($orig_record);
info(L10n::t('Contact has been removed.') . EOL);
if (x($_SESSION, 'return_url')) {
goaway('' . $_SESSION['return_url']);
} else {
goaway('contacts');
}
return; // NOTREACHED
}
if ($cmd === 'posts') {
return contact_posts($a, $contact_id);
}
if ($cmd === 'conversations') {
return contact_conversations($a, $contact_id, $update);
}
}
$_SESSION['return_url'] = $a->query_string;
if ((x($a->data, 'contact')) && (is_array($a->data['contact']))) {
$contact_id = $a->data['contact']['id'];
$contact = $a->data['contact'];
$a->page['htmlhead'] .= replace_macros(get_markup_template('contact_head.tpl'), [
'$baseurl' => System::baseUrl(true),
]);
$a->page['end'] .= replace_macros(get_markup_template('contact_end.tpl'), [
'$baseurl' => System::baseUrl(true),
]);
$contact['blocked'] = Contact::isBlockedByUser($contact['id'], local_user());
$contact['readonly'] = Contact::isIgnoredByUser($contact['id'], local_user());
$dir_icon = '';
$relation_text = '';
switch ($contact['rel']) {
case Contact::FRIEND:
$dir_icon = 'images/lrarrow.gif';
$relation_text = L10n::t('You are mutual friends with %s');
break;
case Contact::FOLLOWER;
$dir_icon = 'images/larrow.gif';
$relation_text = L10n::t('You are sharing with %s');
break;
case Contact::SHARING;
$dir_icon = 'images/rarrow.gif';
$relation_text = L10n::t('%s is sharing with you');
break;
default:
break;
}
if ($contact['uid'] == 0) {
$relation_text = '';
}
if (!in_array($contact['network'], [Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA])) {
$relation_text = "";
}
$relation_text = sprintf($relation_text, htmlentities($contact['name']));
$url = Contact::magicLink($contact['url']);
if (strpos($url, 'redir/') === 0) {
$sparkle = ' class="sparkle" ';
} else {
$sparkle = '';
}
$insecure = L10n::t('Private communications are not available for this contact.');
$last_update = (($contact['last-update'] <= NULL_DATE) ? L10n::t('Never') : DateTimeFormat::local($contact['last-update'], 'D, j M Y, g:i A'));
if ($contact['last-update'] > NULL_DATE) {
$last_update .= ' ' . (($contact['last-update'] <= $contact['success_update']) ? L10n::t("\x28Update was successful\x29") : L10n::t("\x28Update was not successful\x29"));
}
$lblsuggest = (($contact['network'] === Protocol::DFRN) ? L10n::t('Suggest friends') : '');
$poll_enabled = in_array($contact['network'], [Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
$nettype = L10n::t('Network type: %s', ContactSelector::networkToName($contact['network'], $contact["url"]));
// tabs
$tab_str = contacts_tab($a, $contact, 3);
$lost_contact = (($contact['archive'] && $contact['term-date'] > NULL_DATE && $contact['term-date'] < DateTimeFormat::utcNow()) ? L10n::t('Communications lost with this contact!') : '');
$fetch_further_information = null;
if ($contact['network'] == Protocol::FEED) {
$fetch_further_information = [
'fetch_further_information',
L10n::t('Fetch further information for feeds'),
$contact['fetch_further_information'],
L10n::t("Fetch information like preview pictures, title and teaser from the feed item. You can activate this if the feed doesn't contain much text. Keywords are taken from the meta header in the feed item and are posted as hash tags."),
['0' => L10n::t('Disabled'),
'1' => L10n::t('Fetch information'),
'3' => L10n::t('Fetch keywords'),
'2' => L10n::t('Fetch information and keywords')
]
];
}
$poll_interval = null;
if (in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) {
$poll_interval = ContactSelector::pollInterval($contact['priority'], (!$poll_enabled));
}
$profile_select = null;
if ($contact['network'] == Protocol::DFRN) {
$profile_select = ContactSelector::profileAssign($contact['profile-id'], (($contact['network'] !== Protocol::DFRN) ? true : false));
}
/// @todo Only show the following link with DFRN when the remote version supports it
$follow = '';
$follow_text = '';
if (in_array($contact['network'], [Protocol::DIASPORA, Protocol::OSTATUS, Protocol::DFRN])) {
if (in_array($contact['rel'], [Contact::FRIEND, Contact::SHARING])) {
$follow = System::baseUrl(true) . "/unfollow?url=" . urlencode($contact["url"]);
$follow_text = L10n::t("Disconnect/Unfollow");
} else {
$follow = System::baseUrl(true) . "/follow?url=" . urlencode($contact["url"]);
$follow_text = L10n::t("Connect/Follow");
}
}
if ($contact['uid'] == 0) {
$follow = System::baseUrl(true) . "/follow?url=" . urlencode($contact["url"]);
$follow_text = L10n::t("Connect/Follow");
}
// Load contactact related actions like hide, suggest, delete and others
$contact_actions = contact_actions($contact);
if ($contact['uid'] != 0) {
$lbl_vis1 = L10n::t('Profile Visibility');
$lbl_info1 = L10n::t('Contact Information / Notes');
$contact_settings_label = L10n::t('Contact Settings');
} else {
$lbl_vis1 = null;
$lbl_info1 = null;
$contact_settings_label = null;
}
$tpl = get_markup_template("contact_edit.tpl");
$o .= replace_macros($tpl, [
'$header' => L10n::t("Contact"),
'$tab_str' => $tab_str,
'$submit' => L10n::t('Submit'),
'$lbl_vis1' => $lbl_vis1,
'$lbl_vis2' => L10n::t('Please choose the profile you would like to display to %s when viewing your profile securely.', $contact['name']),
'$lbl_info1' => $lbl_info1,
'$lbl_info2' => L10n::t('Their personal note'),
'$reason' => trim(notags($contact['reason'])),
'$infedit' => L10n::t('Edit contact notes'),
'$common_link' => 'common/loc/' . local_user() . '/' . $contact['id'],
'$relation_text' => $relation_text,
'$visit' => L10n::t('Visit %s\'s profile [%s]', $contact['name'], $contact['url']),
'$blockunblock' => L10n::t('Block/Unblock contact'),
'$ignorecont' => L10n::t('Ignore contact'),
'$lblcrepair' => L10n::t("Repair URL settings"),
'$lblrecent' => L10n::t('View conversations'),
'$lblsuggest' => $lblsuggest,
'$nettype' => $nettype,
'$poll_interval' => $poll_interval,
'$poll_enabled' => $poll_enabled,
'$lastupdtext' => L10n::t('Last update:'),
'$lost_contact' => $lost_contact,
'$updpub' => L10n::t('Update public posts'),
'$last_update' => $last_update,
'$udnow' => L10n::t('Update now'),
'$follow' => $follow,
'$follow_text' => $follow_text,
'$profile_select' => $profile_select,
'$contact_id' => $contact['id'],
'$block_text' => (($contact['blocked']) ? L10n::t('Unblock') : L10n::t('Block') ),
'$ignore_text' => (($contact['readonly']) ? L10n::t('Unignore') : L10n::t('Ignore') ),
'$insecure' => (($contact['network'] !== Protocol::DFRN && $contact['network'] !== Protocol::MAIL && $contact['network'] !== Protocol::DIASPORA) ? $insecure : ''),
'$info' => $contact['info'],
'$cinfo' => ['info', '', $contact['info'], ''],
'$blocked' => (($contact['blocked']) ? L10n::t('Currently blocked') : ''),
'$ignored' => (($contact['readonly']) ? L10n::t('Currently ignored') : ''),
'$archived' => (($contact['archive']) ? L10n::t('Currently archived') : ''),
'$pending' => (($contact['pending']) ? L10n::t('Awaiting connection acknowledge') : ''),
'$hidden' => ['hidden', L10n::t('Hide this contact from others'), ($contact['hidden'] == 1), L10n::t('Replies/likes to your public posts <strong>may</strong> still be visible')],
'$notify' => ['notify', L10n::t('Notification for new posts'), ($contact['notify_new_posts'] == 1), L10n::t('Send a notification of every new post of this contact')],
'$fetch_further_information' => $fetch_further_information,
'$ffi_keyword_blacklist' => $contact['ffi_keyword_blacklist'],
'$ffi_keyword_blacklist' => ['ffi_keyword_blacklist', L10n::t('Blacklisted keywords'), $contact['ffi_keyword_blacklist'], L10n::t('Comma separated list of keywords that should not be converted to hashtags, when "Fetch information and keywords" is selected')],
'$photo' => $contact['photo'],
'$name' => htmlentities($contact['name']),
'$dir_icon' => $dir_icon,
'$sparkle' => $sparkle,
'$url' => $url,
'$profileurllabel' => L10n::t('Profile URL'),
'$profileurl' => $contact['url'],
'$account_type' => Contact::getAccountType($contact),
'$location' => BBCode::convert($contact["location"]),
'$location_label' => L10n::t("Location:"),
'$xmpp' => BBCode::convert($contact["xmpp"]),
'$xmpp_label' => L10n::t("XMPP:"),
'$about' => BBCode::convert($contact["about"], false),
'$about_label' => L10n::t("About:"),
'$keywords' => $contact["keywords"],
'$keywords_label' => L10n::t("Tags:"),
'$contact_action_button' => L10n::t("Actions"),
'$contact_actions' => $contact_actions,
'$contact_status' => L10n::t("Status"),
'$contact_settings_label' => $contact_settings_label,
'$contact_profile_label' => L10n::t("Profile"),
]);
$arr = ['contact' => $contact, 'output' => $o];
Addon::callHooks('contact_edit', $arr);
return $arr['output'];
}
$blocked = false;
$hidden = false;
$ignored = false;
$archived = false;
$all = false;
if (($a->argc == 2) && ($a->argv[1] === 'all')) {
$sql_extra = '';
$all = true;
} elseif (($a->argc == 2) && ($a->argv[1] === 'blocked')) {
$sql_extra = " AND `blocked` = 1 ";
$blocked = true;
} elseif (($a->argc == 2) && ($a->argv[1] === 'hidden')) {
$sql_extra = " AND `hidden` = 1 ";
$hidden = true;
} elseif (($a->argc == 2) && ($a->argv[1] === 'ignored')) {
$sql_extra = " AND `readonly` = 1 ";
$ignored = true;
} elseif (($a->argc == 2) && ($a->argv[1] === 'archived')) {
$sql_extra = " AND `archive` = 1 ";
$archived = true;
} else {
$sql_extra = " AND `blocked` = 0 ";
}
$search = x($_GET, 'search') ? notags(trim($_GET['search'])) : '';
$nets = x($_GET, 'nets' ) ? notags(trim($_GET['nets'])) : '';
$tabs = [
[
'label' => L10n::t('Suggestions'),
'url' => 'suggest',
'sel' => '',
'title' => L10n::t('Suggest potential friends'),
'id' => 'suggestions-tab',
'accesskey' => 'g',
],
[
'label' => L10n::t('All Contacts'),
'url' => 'contacts/all',
'sel' => ($all) ? 'active' : '',
'title' => L10n::t('Show all contacts'),
'id' => 'showall-tab',
'accesskey' => 'l',
],
[
'label' => L10n::t('Unblocked'),
'url' => 'contacts',
'sel' => ((!$all) && (!$blocked) && (!$hidden) && (!$search) && (!$nets) && (!$ignored) && (!$archived)) ? 'active' : '',
'title' => L10n::t('Only show unblocked contacts'),
'id' => 'showunblocked-tab',
'accesskey' => 'o',
],
[
'label' => L10n::t('Blocked'),
'url' => 'contacts/blocked',
'sel' => ($blocked) ? 'active' : '',
'title' => L10n::t('Only show blocked contacts'),
'id' => 'showblocked-tab',
'accesskey' => 'b',
],
[
'label' => L10n::t('Ignored'),
'url' => 'contacts/ignored',
'sel' => ($ignored) ? 'active' : '',
'title' => L10n::t('Only show ignored contacts'),
'id' => 'showignored-tab',
'accesskey' => 'i',
],
[
'label' => L10n::t('Archived'),
'url' => 'contacts/archived',
'sel' => ($archived) ? 'active' : '',
'title' => L10n::t('Only show archived contacts'),
'id' => 'showarchived-tab',
'accesskey' => 'y',
],
[
'label' => L10n::t('Hidden'),
'url' => 'contacts/hidden',
'sel' => ($hidden) ? 'active' : '',
'title' => L10n::t('Only show hidden contacts'),
'id' => 'showhidden-tab',
'accesskey' => 'h',
],
];
$tab_tpl = get_markup_template('common_tabs.tpl');
$t = replace_macros($tab_tpl, ['$tabs' => $tabs]);
$total = 0;
$searching = false;
$search_hdr = null;
if ($search) {
$searching = true;
$search_hdr = $search;
$search_txt = DBA::escape(protect_sprintf(preg_quote($search)));
$sql_extra .= " AND (name REGEXP '$search_txt' OR url REGEXP '$search_txt' OR nick REGEXP '$search_txt') ";
}
if ($nets) {
$sql_extra .= sprintf(" AND network = '%s' ", DBA::escape($nets));
}
$sql_extra2 = ((($sort_type > 0) && ($sort_type <= Contact::FRIEND)) ? sprintf(" AND `rel` = %d ", intval($sort_type)) : '');
$r = q("SELECT COUNT(*) AS `total` FROM `contact`
WHERE `uid` = %d AND `self` = 0 AND `pending` = 0 $sql_extra $sql_extra2 ",
intval($_SESSION['uid'])
);
if (DBA::isResult($r)) {
$a->set_pager_total($r[0]['total']);
$total = $r[0]['total'];
}
$sql_extra3 = Widget::unavailableNetworks();
$contacts = [];
$r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `pending` = 0 $sql_extra $sql_extra2 $sql_extra3 ORDER BY `name` ASC LIMIT %d , %d ",
intval($_SESSION['uid']),
intval($a->pager['start']),
intval($a->pager['itemspage'])
);
if (DBA::isResult($r)) {
foreach ($r as $rr) {
$rr['blocked'] = Contact::isBlockedByUser($rr['id'], local_user());
$rr['readonly'] = Contact::isIgnoredByUser($rr['id'], local_user());
$contacts[] = _contact_detail_for_template($rr);
}
}
$tpl = get_markup_template("contacts-template.tpl");
$o .= replace_macros($tpl, [
'$baseurl' => System::baseUrl(),
'$header' => L10n::t('Contacts') . (($nets) ? ' - ' . ContactSelector::networkToName($nets) : ''),
'$tabs' => $t,
'$total' => $total,
'$search' => $search_hdr,
'$desc' => L10n::t('Search your contacts'),
'$finding' => $searching ? L10n::t('Results for: %s', $search) : "",
'$submit' => L10n::t('Find'),
'$cmd' => $a->cmd,
'$contacts' => $contacts,
'$contact_drop_confirm' => L10n::t('Do you really want to delete this contact?'),
'multiselect' => 1,
'$batch_actions' => [
'contacts_batch_update' => L10n::t('Update'),
'contacts_batch_block' => L10n::t('Block') . "/" . L10n::t("Unblock"),
"contacts_batch_ignore" => L10n::t('Ignore') . "/" . L10n::t("Unignore"),
"contacts_batch_archive" => L10n::t('Archive') . "/" . L10n::t("Unarchive"),
"contacts_batch_drop" => L10n::t('Delete'),
],
'$h_batch_actions' => L10n::t('Batch Actions'),
'$paginate' => paginate($a),
]);
return $o;
}
/**
* @brief List of pages for the Contact TabBar
*
* Available Pages are 'Status', 'Profile', 'Contacts' and 'Common Friends'
*
* @param App $a
* @param array $contact The contact array
* @param int $active_tab 1 if tab should be marked as active
*
* @return string
*/
function contacts_tab($a, $contact, $active_tab)
{
// tabs
$tabs = [
[
'label' => L10n::t('Status'),
'url' => "contacts/" . $contact['id'] . "/conversations",
'sel' => (($active_tab == 1) ? 'active' : ''),
'title' => L10n::t('Conversations started by this contact'),
'id' => 'status-tab',
'accesskey' => 'm',
],
[
'label' => L10n::t('Posts and Comments'),
'url' => "contacts/" . $contact['id'] . "/posts",
'sel' => (($active_tab == 2) ? 'active' : ''),
'title' => L10n::t('Status Messages and Posts'),
'id' => 'posts-tab',
'accesskey' => 'p',
],
[
'label' => L10n::t('Profile'),
'url' => "contacts/" . $contact['id'],
'sel' => (($active_tab == 3) ? 'active' : ''),
'title' => L10n::t('Profile Details'),
'id' => 'profile-tab',
'accesskey' => 'o',
]
];
// Show this tab only if there is visible friend list
$x = GContact::countAllFriends(local_user(), $contact['id']);
if ($x) {
$tabs[] = ['label' => L10n::t('Contacts'),
'url' => "allfriends/" . $contact['id'],
'sel' => (($active_tab == 4) ? 'active' : ''),
'title' => L10n::t('View all contacts'),
'id' => 'allfriends-tab',
'accesskey' => 't'];
}
// Show this tab only if there is visible common friend list
$common = GContact::countCommonFriends(local_user(), $contact['id']);
if ($common) {
$tabs[] = ['label' => L10n::t('Common Friends'),
'url' => "common/loc/" . local_user() . "/" . $contact['id'],
'sel' => (($active_tab == 5) ? 'active' : ''),
'title' => L10n::t('View all common friends'),
'id' => 'common-loc-tab',
'accesskey' => 'd'
];
}
if (!empty($contact['uid'])) {
$tabs[] = ['label' => L10n::t('Advanced'),
'url' => 'crepair/' . $contact['id'],
'sel' => (($active_tab == 6) ? 'active' : ''),
'title' => L10n::t('Advanced Contact Settings'),
'id' => 'advanced-tab',
'accesskey' => 'r'
];
}
$tab_tpl = get_markup_template('common_tabs.tpl');
$tab_str = replace_macros($tab_tpl, ['$tabs' => $tabs]);
return $tab_str;
}
function contact_conversations(App $a, $contact_id, $update)
{
$o = '';
if (!$update) {
// We need the editor here to be able to reshare an item.
if (local_user()) {
$x = [
'is_owner' => true,
'allow_location' => $a->user['allow_location'],
'default_location' => $a->user['default-location'],
'nickname' => $a->user['nickname'],
'lockstate' => (is_array($a->user) && (strlen($a->user['allow_cid']) || strlen($a->user['allow_gid']) || strlen($a->user['deny_cid']) || strlen($a->user['deny_gid'])) ? 'lock' : 'unlock'),
'acl' => ACL::getFullSelectorHTML($a->user, true),
'bang' => '',
'visitor' => 'block',
'profile_uid' => local_user(),
];
$o = status_editor($a, $x, 0, true);
}
}
$contact = DBA::selectFirst('contact', ['uid', 'url', 'id'], ['id' => $contact_id]);
if (!$update) {
$o .= contacts_tab($a, $contact, 1);
}
if (DBA::isResult($contact)) {
$a->page['aside'] = "";
$profiledata = Contact::getDetailsByURL($contact["url"]);
if (local_user()) {
if (in_array($profiledata["network"], [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
$profiledata["remoteconnect"] = System::baseUrl()."/follow?url=".urlencode($profiledata["url"]);
}
}
Profile::load($a, "", 0, $profiledata, true);
$o .= Contact::getPostsFromUrl($contact["url"], true, $update);
}
return $o;
}
function contact_posts(App $a, $contact_id)
{
$contact = DBA::selectFirst('contact', ['uid', 'url', 'id'], ['id' => $contact_id]);
$o = contacts_tab($a, $contact, 2);
if (DBA::isResult($contact)) {
$a->page['aside'] = "";
$profiledata = Contact::getDetailsByURL($contact["url"]);
if (local_user()) {
if (in_array($profiledata["network"], [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
$profiledata["remoteconnect"] = System::baseUrl()."/follow?url=".urlencode($profiledata["url"]);
}
}
Profile::load($a, "", 0, $profiledata, true);
$o .= Contact::getPostsFromUrl($contact["url"]);
}
return $o;
}
function _contact_detail_for_template(array $rr)
{
$dir_icon = '';
$alt_text = '';
switch ($rr['rel']) {
case Contact::FRIEND:
$dir_icon = 'images/lrarrow.gif';
$alt_text = L10n::t('Mutual Friendship');
break;
case Contact::FOLLOWER;
$dir_icon = 'images/larrow.gif';
$alt_text = L10n::t('is a fan of yours');
break;
case Contact::SHARING;
$dir_icon = 'images/rarrow.gif';
$alt_text = L10n::t('you are a fan of');
break;
default:
break;
}
$url = Contact::magicLink($rr['url']);
if (strpos($url, 'redir/') === 0) {
$sparkle = ' class="sparkle" ';
} else {
$sparkle = '';
}
if ($rr['self']) {
$dir_icon = 'images/larrow.gif';
$alt_text = L10n::t('This is you');
$url = $rr['url'];
$sparkle = '';
}
return [
'img_hover' => L10n::t('Visit %s\'s profile [%s]', $rr['name'], $rr['url']),
'edit_hover' => L10n::t('Edit contact'),
'photo_menu' => Contact::photoMenu($rr),
'id' => $rr['id'],
'alt_text' => $alt_text,
'dir_icon' => $dir_icon,
'thumb' => ProxyUtils::proxifyUrl($rr['thumb'], false, ProxyUtils::SIZE_THUMB),
'name' => htmlentities($rr['name']),
'username' => htmlentities($rr['name']),
'account_type' => Contact::getAccountType($rr),
'sparkle' => $sparkle,
'itemurl' => (($rr['addr'] != "") ? $rr['addr'] : $rr['url']),
'url' => $url,
'network' => ContactSelector::networkToName($rr['network'], $rr['url']),
'nick' => htmlentities($rr['nick']),
];
}
/**
* @brief Gives a array with actions which can performed to a given contact
*
* This includes actions like e.g. 'block', 'hide', 'archive', 'delete' and others
*
* @param array $contact Data about the Contact
* @return array with contact related actions
*/
function contact_actions($contact)
{
$poll_enabled = in_array($contact['network'], [Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
$contact_actions = [];
// Provide friend suggestion only for Friendica contacts
if ($contact['network'] === Protocol::DFRN) {
$contact_actions['suggest'] = [
'label' => L10n::t('Suggest friends'),
'url' => 'fsuggest/' . $contact['id'],
'title' => '',
'sel' => '',
'id' => 'suggest',
];
}
if ($poll_enabled) {
$contact_actions['update'] = [
'label' => L10n::t('Update now'),
'url' => 'contacts/' . $contact['id'] . '/update',
'title' => '',
'sel' => '',
'id' => 'update',
];
}
$contact_actions['block'] = [
'label' => (intval($contact['blocked']) ? L10n::t('Unblock') : L10n::t('Block') ),
'url' => 'contacts/' . $contact['id'] . '/block',
'title' => L10n::t('Toggle Blocked status'),
'sel' => (intval($contact['blocked']) ? 'active' : ''),
'id' => 'toggle-block',
];
$contact_actions['ignore'] = [
'label' => (intval($contact['readonly']) ? L10n::t('Unignore') : L10n::t('Ignore') ),
'url' => 'contacts/' . $contact['id'] . '/ignore',
'title' => L10n::t('Toggle Ignored status'),
'sel' => (intval($contact['readonly']) ? 'active' : ''),
'id' => 'toggle-ignore',
];
if ($contact['uid'] != 0) {
$contact_actions['archive'] = [
'label' => (intval($contact['archive']) ? L10n::t('Unarchive') : L10n::t('Archive') ),
'url' => 'contacts/' . $contact['id'] . '/archive',
'title' => L10n::t('Toggle Archive status'),
'sel' => (intval($contact['archive']) ? 'active' : ''),
'id' => 'toggle-archive',
];
$contact_actions['delete'] = [
'label' => L10n::t('Delete'),
'url' => 'contacts/' . $contact['id'] . '/drop',
'title' => L10n::t('Delete contact'),
'sel' => '',
'id' => 'delete',
];
}
return $contact_actions;
}

View file

@ -8,10 +8,8 @@ use Friendica\Core\Config;
use Friendica\Core\L10n;
use Friendica\Core\Protocol;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Model\Profile;
require_once 'mod/contacts.php';
use Friendica\Model;
use Friendica\Module;
function crepair_init(App $a)
{
@ -30,7 +28,7 @@ function crepair_init(App $a)
if (DBA::isResult($contact)) {
$a->data['contact'] = $contact;
Profile::load($a, "", 0, Contact::getDetailsByURL($contact["url"]));
Model\Profile::load($a, "", 0, Model\Contact::getDetailsByURL($contact["url"]));
}
}
@ -82,7 +80,7 @@ function crepair_post(App $a)
if ($photo) {
logger('mod-crepair: updating photo from ' . $photo);
Contact::updateAvatar($photo, local_user(), $contact['id']);
Model\Contact::updateAvatar($photo, local_user(), $contact['id']);
}
if ($r) {
@ -116,7 +114,7 @@ function crepair_content(App $a)
$warning = L10n::t('<strong>WARNING: This is highly advanced</strong> and if you enter incorrect information your communications with this contact may stop working.');
$info = L10n::t('Please use your browser \'Back\' button <strong>now</strong> if you are uncertain what to do on this page.');
$returnaddr = "contacts/$cid";
$returnaddr = "contact/$cid";
$allow_remote_self = Config::get('system', 'allow_users_remote_self');
@ -133,9 +131,9 @@ function crepair_content(App $a)
$remote_self_options = ['0' => L10n::t('No mirroring'), '2' => L10n::t('Mirror as my own posting')];
}
$update_profile = in_array($contact['network'], [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS]);
$update_profile = in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS]);
$tab_str = contacts_tab($a, $contact, 5);
$tab_str = Module\Contact::getTabsHTML($a, $contact, 5);
$tpl = get_markup_template('crepair.tpl');
$o = replace_macros($tpl, [

View file

@ -28,6 +28,7 @@ use Friendica\Model\Group;
use Friendica\Model\User;
use Friendica\Network\Probe;
use Friendica\Protocol\Diaspora;
use Friendica\Protocol\ActivityPub;
use Friendica\Util\Crypto;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Network;
@ -220,7 +221,7 @@ function dfrn_confirm_post(App $a, $handsfree = null)
*
*/
$res = Network::post($dfrn_confirm, $params, null, $redirects, 120);
$res = Network::post($dfrn_confirm, $params, null, $redirects, 120)->getBody();
logger(' Confirm: received data: ' . $res, LOGGER_DATA);
@ -335,10 +336,17 @@ function dfrn_confirm_post(App $a, $handsfree = null)
intval($contact_id)
);
} else {
if ($network == Protocol::ACTIVITYPUB) {
ActivityPub\Transmitter::sendContactAccept($contact['url'], $contact['hub-verify'], $uid);
$pending = true;
} else {
$pending = false;
}
// $network !== Protocol::DFRN
$network = defaults($contact, 'network', Protocol::OSTATUS);
$arr = Probe::uri($contact['url']);
$arr = Probe::uri($contact['url'], $network);
$notify = defaults($contact, 'notify' , $arr['notify']);
$poll = defaults($contact, 'poll' , $arr['poll']);
@ -348,7 +356,7 @@ function dfrn_confirm_post(App $a, $handsfree = null)
$new_relation = $contact['rel'];
$writable = $contact['writable'];
if ($network === Protocol::DIASPORA) {
if (in_array($network, [Protocol::DIASPORA, Protocol::ACTIVITYPUB])) {
if ($duplex) {
$new_relation = Contact::FRIEND;
} else {
@ -362,30 +370,12 @@ function dfrn_confirm_post(App $a, $handsfree = null)
DBA::delete('intro', ['id' => $intro_id]);
$r = q("UPDATE `contact` SET `name-date` = '%s',
`uri-date` = '%s',
`addr` = '%s',
`notify` = '%s',
`poll` = '%s',
`blocked` = 0,
`pending` = 0,
`network` = '%s',
`writable` = %d,
`hidden` = %d,
`rel` = %d
WHERE `id` = %d
",
DBA::escape(DateTimeFormat::utcNow()),
DBA::escape(DateTimeFormat::utcNow()),
DBA::escape($addr),
DBA::escape($notify),
DBA::escape($poll),
DBA::escape($network),
intval($writable),
intval($hidden),
intval($new_relation),
intval($contact_id)
);
$fields = ['name-date' => DateTimeFormat::utcNow(),
'uri-date' => DateTimeFormat::utcNow(), 'addr' => $addr,
'notify' => $notify, 'poll' => $poll, 'blocked' => false,
'pending' => $pending, 'network' => $network,
'writable' => $writable, 'hidden' => $hidden, 'rel' => $new_relation];
DBA::update('contact', $fields, ['id' => $contact_id]);
}
if (!DBA::isResult($r)) {
@ -403,10 +393,14 @@ function dfrn_confirm_post(App $a, $handsfree = null)
Group::addMember(User::getDefaultGroup($uid, $contact["network"]), $contact['id']);
if ($network == Protocol::ACTIVITYPUB && $duplex) {
ActivityPub\Transmitter::sendActivity('Follow', $contact['url'], $uid);
}
// Let's send our user to the contact editor in case they want to
// do anything special with this new friend.
if ($handsfree === null) {
goaway(System::baseUrl() . '/contacts/' . intval($contact_id));
goaway(System::baseUrl() . '/contact/' . intval($contact_id));
} else {
return;
}
@ -610,7 +604,7 @@ function dfrn_confirm_post(App $a, $handsfree = null)
'to_name' => $combined['username'],
'to_email' => $combined['email'],
'uid' => $combined['uid'],
'link' => System::baseUrl() . '/contacts/' . $dfrn_record,
'link' => System::baseUrl() . '/contact/' . $dfrn_record,
'source_name' => ((strlen(stripslashes($combined['name']))) ? stripslashes($combined['name']) : L10n::t('[Name Withheld]')),
'source_link' => $combined['url'],
'source_photo' => $combined['photo'],

View file

@ -79,13 +79,13 @@ function dfrn_notify_post(App $a) {
$condition = [];
switch ($direction) {
case (-1):
$condition = ["`issued-id` = ? OR `dfrn-id` = ?", $dfrn_id, $dfrn_id];
$condition = ["(`issued-id` = ? OR `dfrn-id` = ?) AND `uid` = ?", $dfrn_id, $dfrn_id, $user['uid']];
break;
case 0:
$condition = ['issued-id' => $dfrn_id, 'duplex' => true];
$condition = ['issued-id' => $dfrn_id, 'duplex' => true, 'uid' => $user['uid']];
break;
case 1:
$condition = ['dfrn-id' => $dfrn_id, 'duplex' => true];
$condition = ['dfrn-id' => $dfrn_id, 'duplex' => true, 'uid' => $user['uid']];
break;
default:
System::xmlExit(3, 'Invalid direction');
@ -182,7 +182,7 @@ function dfrn_notify_post(App $a) {
function dfrn_dispatch_public($postdata)
{
$msg = Diaspora::decodeRaw([], $postdata);
$msg = Diaspora::decodeRaw([], $postdata, true);
if (!$msg) {
// We have to fail silently to be able to hand it over to the salmon parser
return false;
@ -287,15 +287,15 @@ function dfrn_notify_content(App $a) {
$condition = [];
switch ($direction) {
case (-1):
$condition = ["`issued-id` = ? OR `dfrn-id` = ?", $dfrn_id, $dfrn_id];
$condition = ["(`issued-id` = ? OR `dfrn-id` = ?) AND `uid` = ?", $dfrn_id, $dfrn_id, $user['uid']];
$my_id = $dfrn_id;
break;
case 0:
$condition = ['issued-id' => $dfrn_id, 'duplex' => true];
$condition = ['issued-id' => $dfrn_id, 'duplex' => true, 'uid' => $user['uid']];
$my_id = '1:' . $dfrn_id;
break;
case 1:
$condition = ['dfrn-id' => $dfrn_id, 'duplex' => true];
$condition = ['dfrn-id' => $dfrn_id, 'duplex' => true, 'uid' => $user['uid']];
$my_id = '0:' . $dfrn_id;
break;
default:
@ -322,8 +322,8 @@ function dfrn_notify_content(App $a) {
$encrypted_id = '';
$id_str = $my_id . '.' . mt_rand(1000,9999);
$prv_key = trim($importer['prvkey']);
$pub_key = trim($importer['pubkey']);
$prv_key = trim($importer['cprvkey']);
$pub_key = trim($importer['cpubkey']);
$dplx = intval($importer['duplex']);
if (($dplx && strlen($prv_key)) || (strlen($prv_key) && !strlen($pub_key))) {

View file

@ -502,7 +502,7 @@ function dfrn_poll_content(App $a)
'dfrn_version' => DFRN_PROTOCOL_VERSION,
'challenge' => $challenge,
'sec' => $sec
]);
])->getBody();
}
$profile = ((DBA::isResult($r) && $r[0]['nickname']) ? $r[0]['nickname'] : $nickname);

View file

@ -173,9 +173,9 @@ function dfrn_request_post(App $a)
Contact::updateAvatar($photo, local_user(), $r[0]["id"], true);
}
$forwardurl = System::baseUrl() . "/contacts/" . $r[0]['id'];
$forwardurl = System::baseUrl() . "/contact/" . $r[0]['id'];
} else {
$forwardurl = System::baseUrl() . "/contacts";
$forwardurl = System::baseUrl() . "/contact";
}
// Allow the blocked remote notification to complete
@ -451,10 +451,10 @@ function dfrn_request_post(App $a)
// Diaspora needs the uri in the format user@domain.tld
// Diaspora will support the remote subscription in a future version
if ($network == Protocol::DIASPORA) {
$uri = $nickname . '@' . $a->get_hostname();
$uri = $nickname . '@' . $a->getHostName();
if ($a->get_path()) {
$uri .= '/' . $a->get_path();
if ($a->getURLPath()) {
$uri .= '/' . $a->getURLPath();
}
$uri = urlencode($uri);
@ -609,7 +609,7 @@ function dfrn_request_content(App $a)
} elseif (x($_GET, 'address') && ($_GET['address'] != "")) {
$myaddr = $_GET['address'];
} elseif (local_user()) {
if (strlen($a->urlpath)) {
if (strlen($a->getURLPath())) {
$myaddr = System::baseUrl() . '/profile/' . $a->user['nickname'];
} else {
$myaddr = $a->user['nickname'] . '@' . substr(System::baseUrl(), strpos(System::baseUrl(), '://') + 3);

View file

@ -16,7 +16,7 @@ use Friendica\Util\Proxy as ProxyUtils;
function directory_init(App $a)
{
$a->set_pager_itemspage(60);
$a->setPagerItemsPage(60);
if (local_user()) {
$a->page['aside'] .= Widget::findPeople();
@ -87,7 +87,7 @@ function directory_content(App $a)
LEFT JOIN `user` ON `user`.`uid` = `profile`.`uid`
WHERE `is-default` $publish AND NOT `user`.`blocked` AND NOT `user`.`account_removed` $sql_extra");
if (DBA::isResult($cnt)) {
$a->set_pager_total($cnt['total']);
$a->setPagerTotal($cnt['total']);
}
$order = " ORDER BY `name` ASC ";

View file

@ -12,14 +12,13 @@ use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Model\GContact;
use Friendica\Model;
use Friendica\Module;
use Friendica\Network\Probe;
use Friendica\Protocol\PortableContact;
use Friendica\Util\Network;
use Friendica\Util\Proxy as ProxyUtils;
require_once 'mod/contacts.php';
function dirfind_init(App $a) {
@ -44,7 +43,7 @@ function dirfind_content(App $a, $prefix = "") {
$local = Config::get('system','poco_local_search');
$search = $prefix.notags(trim($_REQUEST['search']));
$search = $prefix.notags(trim(defaults($_REQUEST, 'search', '')));
$header = '';
@ -54,7 +53,7 @@ function dirfind_content(App $a, $prefix = "") {
if ((valid_email($search) && Network::isEmailDomainValid($search)) ||
(substr(normalise_link($search), 0, 7) == "http://")) {
$user_data = Probe::uri($search);
$discover_user = (in_array($user_data["network"], [Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA]));
$discover_user = (in_array($user_data["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA]));
}
}
@ -83,7 +82,7 @@ function dirfind_content(App $a, $prefix = "") {
$objresult->tags = "";
$objresult->network = $user_data["network"];
$contact = Contact::getDetailsByURL($user_data["url"], local_user());
$contact = Model\Contact::getDetailsByURL($user_data["url"], local_user());
$objresult->cid = $contact["cid"];
$objresult->pcid = $contact["zid"];
@ -91,7 +90,7 @@ function dirfind_content(App $a, $prefix = "") {
// Add the contact to the global contacts if it isn't already in our system
if (($contact["cid"] == 0) && ($contact["zid"] == 0) && ($contact["gid"] == 0)) {
GContact::update($user_data);
Model\GContact::update($user_data);
}
} elseif ($local) {
@ -156,7 +155,7 @@ function dirfind_content(App $a, $prefix = "") {
continue;
}
$result = Contact::getDetailsByURL($result["nurl"], local_user());
$result = Model\Contact::getDetailsByURL($result["nurl"], local_user());
if ($result["name"] == "") {
$result["name"] = end(explode("/", $urlparts["path"]));
@ -188,8 +187,8 @@ function dirfind_content(App $a, $prefix = "") {
}
if ($j->total) {
$a->set_pager_total($j->total);
$a->set_pager_itemspage($j->items_page);
$a->setPagerTotal($j->total);
$a->setPagerItemsPage($j->items_page);
}
if (!empty($j->results)) {
@ -200,7 +199,7 @@ function dirfind_content(App $a, $prefix = "") {
$alt_text = "";
$contact_details = Contact::getDetailsByURL($jj->url, local_user());
$contact_details = Model\Contact::getDetailsByURL($jj->url, local_user());
$itemurl = (($contact_details["addr"] != "") ? $contact_details["addr"] : $jj->url);
@ -210,8 +209,8 @@ function dirfind_content(App $a, $prefix = "") {
$conntxt = "";
$contact = DBA::selectFirst('contact', [], ['id' => $jj->cid]);
if (DBA::isResult($contact)) {
$photo_menu = Contact::photoMenu($contact);
$details = _contact_detail_for_template($contact);
$photo_menu = Model\Contact::photoMenu($contact);
$details = Module\Contact::getContactTemplateVars($contact);
$alt_text = $details['alt_text'];
} else {
$photo_menu = [];
@ -222,12 +221,12 @@ function dirfind_content(App $a, $prefix = "") {
$contact = DBA::selectFirst('contact', [], ['id' => $jj->pcid]);
if (DBA::isResult($contact)) {
$photo_menu = Contact::photoMenu($contact);
$photo_menu = Model\Contact::photoMenu($contact);
} else {
$photo_menu = [];
}
$photo_menu['profile'] = [L10n::t("View Profile"), Contact::magicLink($jj->url)];
$photo_menu['profile'] = [L10n::t("View Profile"), Model\Contact::magicLink($jj->url)];
$photo_menu['follow'] = [L10n::t("Connect/Follow"), $connlnk];
}
@ -235,7 +234,7 @@ function dirfind_content(App $a, $prefix = "") {
$entry = [
'alt_text' => $alt_text,
'url' => Contact::magicLink($jj->url),
'url' => Model\Contact::magicLink($jj->url),
'itemurl' => $itemurl,
'name' => htmlentities($jj->name),
'thumb' => ProxyUtils::proxifyUrl($jj->photo, false, ProxyUtils::SIZE_THUMB),
@ -246,7 +245,7 @@ function dirfind_content(App $a, $prefix = "") {
'details' => $contact_details['location'],
'tags' => $contact_details['keywords'],
'about' => $contact_details['about'],
'account_type' => Contact::getAccountType($contact_details),
'account_type' => Model\Contact::getAccountType($contact_details),
'network' => ContactSelector::networkToName($jj->network, $jj->url),
'id' => ++$id,
];

View file

@ -17,6 +17,7 @@ use Friendica\Model\Group;
use Friendica\Model\Item;
use Friendica\Model\Profile;
use Friendica\Protocol\DFRN;
use Friendica\Protocol\ActivityPub;
function display_init(App $a)
{
@ -43,7 +44,7 @@ function display_init(App $a)
$item = null;
$fields = ['id', 'parent', 'author-id', 'body', 'uid'];
$fields = ['id', 'parent', 'author-id', 'body', 'uid', 'guid'];
// If there is only one parameter, then check if this parameter could be a guid
if ($a->argc == 2) {
@ -76,6 +77,10 @@ function display_init(App $a)
displayShowFeed($item["id"], false);
}
if (ActivityPub::isRequest()) {
goaway(str_replace('display/', 'objects/', $a->query_string));
}
if ($item["id"] != $item["parent"]) {
$item = Item::selectFirstForUser(local_user(), $fields, ['id' => $item["parent"]]);
}
@ -360,7 +365,7 @@ function display_content(App $a, $update = false, $update_uid = 0)
$title = trim(HTML::toPlaintext(BBCode::convert($item["title"], false), 0, true));
$author_name = $item["author-name"];
$image = $a->remove_baseurl($item["author-avatar"]);
$image = $a->removeBaseURL($item["author-avatar"]);
if ($title == "") {
$title = $author_name;

View file

@ -28,7 +28,7 @@ function editpost_content(App $a)
}
$fields = ['allow_cid', 'allow_gid', 'deny_cid', 'deny_gid',
'type', 'body', 'title', 'file', 'wall', 'post-type'];
'type', 'body', 'title', 'file', 'wall', 'post-type', 'guid'];
$item = Item::selectFirstForUser(local_user(), $fields, ['id' => $post_id, 'uid' => local_user()]);
@ -51,15 +51,6 @@ function editpost_content(App $a)
'$nickname' => $a->user['nickname']
]);
$tpl = get_markup_template('jot-end.tpl');
$a->page['end'] .= replace_macros($tpl, [
'$baseurl' => System::baseUrl(),
'$ispublic' => '&nbsp;', // L10n::t('Visible to <strong>everybody</strong>'),
'$geotag' => $geotag,
'$nickname' => $a->user['nickname']
]);
$tpl = get_markup_template("jot.tpl");
if (strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid'])) {
@ -95,7 +86,7 @@ function editpost_content(App $a)
$o .= replace_macros($tpl, [
'$is_edit' => true,
'$return_path' => $_SESSION['return_url'],
'$return_path' => '/display/' . $item['guid'],
'$action' => 'item',
'$share' => L10n::t('Save'),
'$upload' => L10n::t('Upload photo'),

View file

@ -17,10 +17,12 @@ use Friendica\Model\Item;
use Friendica\Model\Profile;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Temporal;
use Friendica\Module\Login;
require_once 'include/items.php';
function events_init(App $a) {
function events_init(App $a)
{
if (!local_user()) {
return;
}
@ -42,7 +44,8 @@ function events_init(App $a) {
return;
}
function events_post(App $a) {
function events_post(App $a)
{
logger('post: ' . print_r($_REQUEST, true), LOGGER_DATA);
@ -50,15 +53,15 @@ function events_post(App $a) {
return;
}
$event_id = (x($_POST, 'event_id') ? intval($_POST['event_id']) : 0);
$cid = (x($_POST, 'cid') ? intval($_POST['cid']) : 0);
$event_id = !empty($_POST['event_id']) ? intval($_POST['event_id']) : 0;
$cid = !empty($_POST['cid']) ? intval($_POST['cid']) : 0;
$uid = local_user();
$start_text = escape_tags($_REQUEST['start_text']);
$finish_text = escape_tags($_REQUEST['finish_text']);
$start_text = escape_tags(defaults($_REQUEST, 'start_text', ''));
$finish_text = escape_tags(defaults($_REQUEST, 'finish_text', ''));
$adjust = intval($_POST['adjust']);
$nofinish = intval($_POST['nofinish']);
$adjust = intval(defaults($_POST, 'adjust', 0));
$nofinish = intval(defaults($_POST, 'nofinish', 0));
// The default setting for the `private` field in event_store() is false, so mirror that
$private_event = false;
@ -91,9 +94,9 @@ function events_post(App $a) {
// and we'll waste a bunch of time responding to it. Time that
// could've been spent doing something else.
$summary = escape_tags(trim($_POST['summary']));
$desc = escape_tags(trim($_POST['desc']));
$location = escape_tags(trim($_POST['location']));
$summary = escape_tags(trim(defaults($_POST, 'summary', '')));
$desc = escape_tags(trim(defaults($_POST, 'desc', '')));
$location = escape_tags(trim(defaults($_POST, 'location', '')));
$type = 'event';
$action = ($event_id == '') ? 'new' : "event/" . $event_id;
@ -117,7 +120,7 @@ function events_post(App $a) {
goaway($onerror_url);
}
$share = (intval($_POST['share']) ? intval($_POST['share']) : 0);
$share = intval(defaults($_POST, 'share', 0));
$c = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `self` LIMIT 1",
intval(local_user())
@ -137,7 +140,7 @@ function events_post(App $a) {
$str_contact_deny = !empty($_POST['contact_deny']) ? perms2str($_POST['contact_deny']) : '';
// Undo the pseudo-contact of self, since there are real contacts now
if (strpos($str_contact_allow, '<' . $self . '>') !== false ) {
if (strpos($str_contact_allow, '<' . $self . '>') !== false) {
$str_contact_allow = str_replace('<' . $self . '>', '', $str_contact_allow);
}
// Make sure to set the `private` field as true. This is necessary to
@ -184,14 +187,14 @@ function events_post(App $a) {
Worker::add(PRIORITY_HIGH, "Notifier", "event", $item_id);
}
goaway($_SESSION['return_url']);
goaway('/events');
}
function events_content(App $a) {
function events_content(App $a)
{
if (!local_user()) {
notice(L10n::t('Permission denied.') . EOL);
return;
return Login::form();
}
if ($a->argc == 1) {
@ -229,11 +232,6 @@ function events_content(App $a) {
'$i18n' => $i18n,
]);
$etpl = get_markup_template('event_end.tpl');
$a->page['end'] .= replace_macros($etpl, [
'$baseurl' => System::baseUrl(),
]);
$o = '';
$tabs = '';
// tabs
@ -244,7 +242,7 @@ function events_content(App $a) {
$mode = 'view';
$y = 0;
$m = 0;
$ignored = (x($_REQUEST, 'ignored') ? intval($_REQUEST['ignored']) : 0);
$ignored = !empty($_REQUEST['ignored']) ? intval($_REQUEST['ignored']) : 0;
if ($a->argc > 1) {
if ($a->argc > 2 && $a->argv[1] == 'event') {
@ -272,7 +270,6 @@ function events_content(App $a) {
// The view mode part is similiar to /mod/cal.php
if ($mode == 'view') {
$thisyear = DateTimeFormat::localNow('Y');
$thismonth = DateTimeFormat::localNow('m');
if (!$y) {
@ -312,10 +309,10 @@ function events_content(App $a) {
$finish = sprintf('%d-%d-%d %d:%d:%d', $y, $m, $dim, 23, 59, 59);
if ($a->argc > 1 && $a->argv[1] === 'json') {
if (x($_GET, 'start')) {
$start = $_GET['start'];
if (!empty($_GET['start'])) {
$start = $_GET['start'];
}
if (x($_GET, 'end')) {
if (!empty($_GET['end'])) {
$finish = $_GET['end'];
}
}
@ -349,7 +346,7 @@ function events_content(App $a) {
$r = Event::sortByDate($r);
foreach ($r as $rr) {
$j = $rr['adjust'] ? DateTimeFormat::local($rr['start'], 'j') : DateTimeFormat::utc($rr['start'], 'j');
if (!x($links,$j)) {
if (empty($links[$j])) {
$links[$j] = System::baseUrl() . '/' . $a->cmd . '#link-' . $j;
}
}
@ -363,12 +360,12 @@ function events_content(App $a) {
$events = Event::prepareListForTemplate($r);
}
if ($a->argc > 1 && $a->argv[1] === 'json'){
if ($a->argc > 1 && $a->argv[1] === 'json') {
echo json_encode($events);
killme();
}
if (x($_GET, 'id')) {
if (!empty($_GET['id'])) {
$tpl = get_markup_template("event.tpl");
} else {
$tpl = get_markup_template("events_js.tpl");
@ -378,7 +375,7 @@ function events_content(App $a) {
foreach ($events as $key => $event) {
$event_item = [];
foreach ($event['item'] as $k => $v) {
$k = str_replace('-' ,'_', $k);
$k = str_replace('-', '_', $k);
$event_item[$k] = $v;
}
$events[$key]['item'] = $event_item;
@ -403,7 +400,7 @@ function events_content(App $a) {
'$list' => L10n::t('list'),
]);
if (x($_GET, 'id')) {
if (!empty($_GET['id'])) {
echo $o;
killme();
}
@ -428,41 +425,45 @@ function events_content(App $a) {
}
// In case of an error the browser is redirected back here, with these parameters filled in with the previous values
if (x($_REQUEST, 'nofinish')) {$orig_event['nofinish'] = $_REQUEST['nofinish'];}
if (x($_REQUEST, 'adjust')) {$orig_event['adjust'] = $_REQUEST['adjust'];}
if (x($_REQUEST, 'summary')) {$orig_event['summary'] = $_REQUEST['summary'];}
if (x($_REQUEST, 'description')) {$orig_event['description'] = $_REQUEST['description'];}
if (x($_REQUEST, 'location')) {$orig_event['location'] = $_REQUEST['location'];}
if (x($_REQUEST, 'start')) {$orig_event['start'] = $_REQUEST['start'];}
if (x($_REQUEST, 'finish')) {$orig_event['finish'] = $_REQUEST['finish'];}
if (x($_REQUEST,'finish')) $orig_event['finish'] = $_REQUEST['finish'];
if (!empty($_REQUEST['nofinish'])) {$orig_event['nofinish'] = $_REQUEST['nofinish'];}
if (!empty($_REQUEST['adjust'])) {$orig_event['adjust'] = $_REQUEST['adjust'];}
if (!empty($_REQUEST['summary'])) {$orig_event['summary'] = $_REQUEST['summary'];}
if (!empty($_REQUEST['description'])) {$orig_event['description'] = $_REQUEST['description'];}
if (!empty($_REQUEST['location'])) {$orig_event['location'] = $_REQUEST['location'];}
if (!empty($_REQUEST['start'])) {$orig_event['start'] = $_REQUEST['start'];}
if (!empty($_REQUEST['finish'])) {$orig_event['finish'] = $_REQUEST['finish'];}
$n_checked = ((x($orig_event) && $orig_event['nofinish']) ? ' checked="checked" ' : '');
$a_checked = ((x($orig_event) && $orig_event['adjust']) ? ' checked="checked" ' : '');
$n_checked = (!empty($orig_event['nofinish']) ? ' checked="checked" ' : '');
$a_checked = (!empty($orig_event['adjust']) ? ' checked="checked" ' : '');
$t_orig = (x($orig_event) ? $orig_event['summary'] : '');
$d_orig = (x($orig_event) ? $orig_event['desc'] : '');
$l_orig = (x($orig_event) ? $orig_event['location'] : '');
$eid = (x($orig_event) ? $orig_event['id'] : 0);
$cid = (x($orig_event) ? $orig_event['cid'] : 0);
$uri = (x($orig_event) ? $orig_event['uri'] : '');
$t_orig = !empty($orig_event) ? $orig_event['summary'] : '';
$d_orig = !empty($orig_event) ? $orig_event['desc'] : '';
$l_orig = !empty($orig_event) ? $orig_event['location'] : '';
$eid = !empty($orig_event) ? $orig_event['id'] : 0;
$cid = !empty($orig_event) ? $orig_event['cid'] : 0;
$uri = !empty($orig_event) ? $orig_event['uri'] : '';
$sh_disabled = '';
$sh_checked = '';
$sh_checked = '';
if (x($orig_event)) {
$sh_checked = (($orig_event['allow_cid'] === '<' . local_user() . '>' && !$orig_event['allow_gid'] && !$orig_event['deny_cid'] && !$orig_event['deny_gid']) ? '' : ' checked="checked" ');
if (!empty($orig_event)
&& ($orig_event['allow_cid'] !== '<' . local_user() . '>'
|| $orig_event['allow_gid']
|| $orig_event['deny_cid']
|| $orig_event['deny_gid']))
{
$sh_checked = ' checked="checked" ';
}
if ($cid || $mode === 'edit') {
$sh_disabled = 'disabled="disabled"';
}
$sdt = (x($orig_event) ? $orig_event['start'] : 'now');
$fdt = (x($orig_event) ? $orig_event['finish'] : 'now');
$sdt = !empty($orig_event) ? $orig_event['start'] : 'now';
$fdt = !empty($orig_event) ? $orig_event['finish'] : 'now';
$tz = date_default_timezone_get();
if (x($orig_event)) {
if (!empty($orig_event)) {
$tz = ($orig_event['adjust'] ? date_default_timezone_get() : 'UTC');
}
@ -470,20 +471,22 @@ function events_content(App $a) {
$smonth = DateTimeFormat::convert($sdt, $tz, 'UTC', 'm');
$sday = DateTimeFormat::convert($sdt, $tz, 'UTC', 'd');
$shour = (x($orig_event) ? DateTimeFormat::convert($sdt, $tz, 'UTC', 'H') : '00');
$sminute = (x($orig_event) ? DateTimeFormat::convert($sdt, $tz, 'UTC', 'i') : '00');
$shour = !empty($orig_event) ? DateTimeFormat::convert($sdt, $tz, 'UTC', 'H') : '00';
$sminute = !empty($orig_event) ? DateTimeFormat::convert($sdt, $tz, 'UTC', 'i') : '00';
$fyear = DateTimeFormat::convert($fdt, $tz, 'UTC', 'Y');
$fmonth = DateTimeFormat::convert($fdt, $tz, 'UTC', 'm');
$fday = DateTimeFormat::convert($fdt, $tz, 'UTC', 'd');
$fhour = (x($orig_event) ? DateTimeFormat::convert($fdt, $tz, 'UTC', 'H') : '00');
$fminute = (x($orig_event) ? DateTimeFormat::convert($fdt, $tz, 'UTC', 'i') : '00');
$fhour = !empty($orig_event) ? DateTimeFormat::convert($fdt, $tz, 'UTC', 'H') : '00';
$fminute = !empty($orig_event) ? DateTimeFormat::convert($fdt, $tz, 'UTC', 'i') : '00';
$perms = ACL::getDefaultUserPermissions($orig_event);
if ($mode === 'new' || $mode === 'copy') {
$acl = ($cid ? '' : ACL::getFullSelectorHTML($a->user, false, $orig_event));
if (!$cid && in_array($mode, ['new', 'copy'])) {
$acl = ACL::getFullSelectorHTML($a->user, false, $orig_event);
} else {
$acl = '';
}
// If we copy an old event, we need to remove the ID and URI
@ -495,7 +498,7 @@ function events_content(App $a) {
$tpl = get_markup_template('event_form.tpl');
$o .= replace_macros($tpl,[
$o .= replace_macros($tpl, [
'$post' => System::baseUrl() . '/events',
'$eid' => $eid,
'$cid' => $cid,
@ -509,11 +512,31 @@ function events_content(App $a) {
'$title' => L10n::t('Event details'),
'$desc' => L10n::t('Starting date and Title are required.'),
'$s_text' => L10n::t('Event Starts:') . ' <span class="required" title="' . L10n::t('Required') . '">*</span>',
'$s_dsel' => Temporal::getDateTimeField(new DateTime(), DateTime::createFromFormat('Y', $syear+5), DateTime::createFromFormat('Y-m-d H:i', "$syear-$smonth-$sday $shour:$sminute"), L10n::t('Event Starts:'), 'start_text', true, true, '', '', true),
'$s_dsel' => Temporal::getDateTimeField(
new DateTime(),
DateTime::createFromFormat('Y', $syear+5),
DateTime::createFromFormat('Y-m-d H:i', "$syear-$smonth-$sday $shour:$sminute"),
L10n::t('Event Starts:'),
'start_text',
true,
true,
'',
'',
true
),
'$n_text' => L10n::t('Finish date/time is not known or not relevant'),
'$n_checked' => $n_checked,
'$f_text' => L10n::t('Event Finishes:'),
'$f_dsel' => Temporal::getDateTimeField(new DateTime(), DateTime::createFromFormat('Y', $fyear+5), DateTime::createFromFormat('Y-m-d H:i', "$fyear-$fmonth-$fday $fhour:$fminute"), L10n::t('Event Finishes:'), 'finish_text', true, true, 'start_text'),
'$f_dsel' => Temporal::getDateTimeField(
new DateTime(),
DateTime::createFromFormat('Y', $fyear+5),
DateTime::createFromFormat('Y-m-d H:i', "$fyear-$fmonth-$fday $fhour:$fminute"),
L10n::t('Event Finishes:'),
'finish_text',
true,
true,
'start_text'
),
'$a_text' => L10n::t('Adjust for viewer timezone'),
'$a_checked' => $a_checked,
'$d_text' => L10n::t('Description:'),
@ -534,7 +557,6 @@ function events_content(App $a) {
'$basic' => L10n::t('Basic'),
'$advanced' => L10n::t('Advanced'),
'$permissions' => L10n::t('Permissions'),
]);
return $o;

View file

@ -32,8 +32,7 @@ function feedtest_content(App $a)
$contact = DBA::selectFirst('contact', [], ['id' => $contact_id]);
$ret = Network::curl($contact['poll']);
$xml = $ret['body'];
$xml = Network::fetchUrl($contact['poll']);
$dummy = null;
$import_result = Feed::import($xml, $importer, $contact, $dummy, true);

View file

@ -25,7 +25,7 @@ function fetch_init(App $a)
// Fetch the item
$fields = ['uid', 'title', 'body', 'guid', 'contact-id', 'private', 'created', 'app', 'location', 'coord', 'network',
'event-id', 'resource-id', 'author-link', 'owner-link', 'attach'];
'event-id', 'resource-id', 'author-link', 'author-avatar', 'author-name', 'plink', 'owner-link', 'attach'];
$condition = ['wall' => true, 'private' => false, 'guid' => $guid, 'network' => [Protocol::DFRN, Protocol::DIASPORA]];
$item = Item::selectFirst($fields, $condition);
if (!DBA::isResult($item)) {

View file

@ -25,9 +25,7 @@ function filerm_content(App $a) {
file_tag_unsave_file(local_user(),$item_id,$term, $category);
}
if (x($_SESSION,'return_url')) {
goaway(System::baseUrl() . '/' . $_SESSION['return_url']);
}
//goaway('/network');
killme();
}

View file

@ -20,12 +20,12 @@ function follow_post(App $a)
}
if (isset($_REQUEST['cancel'])) {
goaway($_SESSION['return_url']);
goaway('contacts');
}
$uid = local_user();
$url = notags(trim($_REQUEST['url']));
$return_url = $_SESSION['return_url'];
$return_url = 'contacts';
// Makes the connection request for friendica contacts easier
// This is just a precaution if maybe this page is called somewhere directly via POST
@ -39,7 +39,7 @@ function follow_post(App $a)
}
goaway($return_url);
} elseif ($result['cid']) {
goaway(System::baseUrl() . '/contacts/' . $result['cid']);
goaway('contact/' . $result['cid']);
}
info(L10n::t('The contact could not be added.'));
@ -50,9 +50,11 @@ function follow_post(App $a)
function follow_content(App $a)
{
$return_url = 'contacts';
if (!local_user()) {
notice(L10n::t('Permission denied.'));
goaway($_SESSION['return_url']);
goaway($return_url);
// NOTREACHED
}
@ -116,7 +118,7 @@ function follow_content(App $a)
if (!$r) {
notice(L10n::t('Permission denied.'));
goaway($_SESSION['return_url']);
goaway($return_url);
// NOTREACHED
}

View file

@ -36,7 +36,7 @@ function fsuggest_post(App $a)
$hash = random_string();
$note = escape_tags(trim($_POST['note']));
$note = escape_tags(trim(defaults($_POST, 'note', '')));
if ($new_contact) {
$r = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",

View file

@ -11,12 +11,12 @@ use Friendica\Core\L10n;
use Friendica\Core\PConfig;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Model\Group;
use Friendica\Model;
use Friendica\Module;
function group_init(App $a) {
if (local_user()) {
$a->page['aside'] = Group::sidebarWidget('contacts', 'group', 'extended', (($a->argc > 1) ? $a->argv[1] : 'everyone'));
$a->page['aside'] = Model\Group::sidebarWidget('contacts', 'group', 'extended', (($a->argc > 1) ? $a->argv[1] : 'everyone'));
}
}
@ -31,10 +31,10 @@ function group_post(App $a) {
check_form_security_token_redirectOnErr('/group/new', 'group_edit');
$name = notags(trim($_POST['groupname']));
$r = Group::create(local_user(), $name);
$r = Model\Group::create(local_user(), $name);
if ($r) {
info(L10n::t('Group created.') . EOL);
$r = Group::getIdByName(local_user(), $name);
$r = Model\Group::getIdByName(local_user(), $name);
if ($r) {
goaway(System::baseUrl() . '/group/' . $r);
}
@ -54,7 +54,7 @@ function group_post(App $a) {
);
if (!DBA::isResult($r)) {
notice(L10n::t('Group not found.') . EOL);
goaway(System::baseUrl() . '/contacts');
goaway(System::baseUrl() . '/contact');
return; // NOTREACHED
}
$group = $r[0];
@ -71,7 +71,7 @@ function group_post(App $a) {
}
}
$a->page['aside'] = Group::sidebarWidget();
$a->page['aside'] = Model\Group::sidebarWidget();
}
return;
}
@ -116,8 +116,6 @@ function group_content(App $a) {
$nogroup = false;
if (($a->argc == 2) && ($a->argv[1] === 'none')) {
require_once 'mod/contacts.php';
$id = -1;
$nogroup = true;
$group = [
@ -150,7 +148,7 @@ function group_content(App $a) {
$result = null;
if (DBA::isResult($r)) {
$result = Group::removeByName(local_user(), $r[0]['name']);
$result = Model\Group::removeByName(local_user(), $r[0]['name']);
}
if ($result) {
@ -176,8 +174,6 @@ function group_content(App $a) {
}
if (($a->argc > 1) && intval($a->argv[1])) {
require_once 'mod/contacts.php';
$r = q("SELECT * FROM `group` WHERE `id` = %d AND `uid` = %d AND `deleted` = 0 LIMIT 1",
intval($a->argv[1]),
intval(local_user())
@ -185,11 +181,11 @@ function group_content(App $a) {
if (!DBA::isResult($r)) {
notice(L10n::t('Group not found.') . EOL);
goaway(System::baseUrl() . '/contacts');
goaway(System::baseUrl() . '/contact');
}
$group = $r[0];
$members = Contact::getByGroupId($group['id']);
$members = Model\Contact::getByGroupId($group['id']);
$preselected = [];
$entry = [];
$id = 0;
@ -202,12 +198,12 @@ function group_content(App $a) {
if ($change) {
if (in_array($change, $preselected)) {
Group::removeMember($group['id'], $change);
Model\Group::removeMember($group['id'], $change);
} else {
Group::addMember($group['id'], $change);
Model\Group::addMember($group['id'], $change);
}
$members = Contact::getByGroupId($group['id']);
$members = Model\Contact::getByGroupId($group['id']);
$preselected = [];
if (count($members)) {
foreach ($members as $member) {
@ -253,7 +249,7 @@ function group_content(App $a) {
// Format the data of the group members
foreach ($members as $member) {
if ($member['url']) {
$entry = _contact_detail_for_template($member);
$entry = Module\Contact::getContactTemplateVars($member);
$entry['label'] = 'members';
$entry['photo_menu'] = '';
$entry['change_member'] = [
@ -265,12 +261,12 @@ function group_content(App $a) {
$groupeditor['members'][] = $entry;
} else {
Group::removeMember($group['id'], $member['id']);
Model\Group::removeMember($group['id'], $member['id']);
}
}
if ($nogroup) {
$r = Contact::getUngroupedList(local_user());
$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())
@ -282,7 +278,7 @@ function group_content(App $a) {
// Format the data of the contacts who aren't in the contact group
foreach ($r as $member) {
if (!in_array($member['id'], $preselected)) {
$entry = _contact_detail_for_template($member);
$entry = Module\Contact::getContactTemplateVars($member);
$entry['label'] = 'contacts';
if (!$nogroup)
$entry['photo_menu'] = [];

View file

@ -50,7 +50,7 @@ function hcard_init(App $a)
$a->page['htmlhead'] .= '<meta name="dfrn-global-visibility" content="' . (($a->profile['net-publish']) ? 'true' : 'false') . '" />' . "\r\n" ;
$a->page['htmlhead'] .= '<link rel="alternate" type="application/atom+xml" href="' . System::baseUrl() . '/dfrn_poll/' . $which .'" />' . "\r\n" ;
$uri = urlencode('acct:' . $a->profile['nickname'] . '@' . $a->get_hostname() . (($a->urlpath) ? '/' . $a->urlpath : ''));
$uri = urlencode('acct:' . $a->profile['nickname'] . '@' . $a->getHostName() . (($a->getURLPath()) ? '/' . $a->getURLPath() : ''));
$a->page['htmlhead'] .= '<link rel="lrdd" type="application/xrd+xml" href="' . System::baseUrl() . '/xrd/?uri=' . $uri . '" />' . "\r\n";
header('Link: <' . System::baseUrl() . '/xrd/?uri=' . $uri . '>; rel="lrdd"; type="application/xrd+xml"', false);

View file

@ -36,12 +36,12 @@ function help_content(App $a)
$path = '';
// looping through the argv keys bigger than 0 to build
// a path relative to /help
for ($x = 1; $x < argc(); $x ++) {
for ($x = 1; $x < $a->argc; $x ++) {
if (strlen($path)) {
$path .= '/';
}
$path .= argv($x);
$path .= $a->getArgumentValue($x);
}
$title = basename($path);
$filename = $path;

View file

@ -38,8 +38,8 @@ function home_content(App $a) {
$customhome = false;
$defaultheader = '<h1>' . (Config::get('config', 'sitename') ? L10n::t('Welcome to %s', Config::get('config', 'sitename')) : '') . '</h1>';
$homefilepath = $a->basepath . "/home.html";
$cssfilepath = $a->basepath . "/home.css";
$homefilepath = $a->getBasePath() . "/home.html";
$cssfilepath = $a->getBasePath() . "/home.css";
if (file_exists($homefilepath)) {
$customhome = $homefilepath;
if (file_exists($cssfilepath)) {

View file

@ -23,7 +23,7 @@ function hostxrd_init(App $a)
$tpl = get_markup_template('xrd_host.tpl');
echo replace_macros($tpl, [
'$zhost' => $a->get_hostname(),
'$zhost' => $a->getHostName(),
'$zroot' => System::baseUrl(),
'$domain' => System::baseUrl(),
'$bigkey' => Salmon::salmonKey(Config::get('system', 'site_pubkey'))]

View file

@ -8,6 +8,7 @@ use Friendica\Core\Install;
use Friendica\Core\L10n;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use Friendica\Util\Temporal;
$install_wizard_pass = 1;
@ -42,7 +43,6 @@ function install_post(App $a) {
return;
break; // just in case return don't return :)
case 3:
$urlpath = $a->get_path();
$dbhost = notags(trim($_POST['dbhost']));
$dbuser = notags(trim($_POST['dbuser']));
$dbpass = notags(trim($_POST['dbpass']));
@ -57,7 +57,7 @@ function install_post(App $a) {
return;
break;
case 4:
$urlpath = $a->get_path();
$urlpath = $a->getURLPath();
$dbhost = notags(trim($_POST['dbhost']));
$dbuser = notags(trim($_POST['dbuser']));
$dbpass = notags(trim($_POST['dbpass']));
@ -70,14 +70,16 @@ function install_post(App $a) {
// connect to db
DBA::connect($dbhost, $dbuser, $dbpass, $dbdata);
$errors = Install::createConfig($urlpath, $dbhost, $dbuser, $dbpass, $dbdata, $phpath, $timezone, $language, $adminmail);
$install = new Install();
if ($errors) {
$a->data['db_failed'] = $errors;
$errors = $install->createConfig($phpath, $urlpath, $dbhost, $dbuser, $dbpass, $dbdata, $timezone, $language, $adminmail, $a->getBasePath());
if ($errors !== true) {
$a->data['data'] = $errors;
return;
}
$errors = Install::installDatabaseStructure();
$errors = DBStructure::update(false, true, true);
if ($errors) {
$a->data['db_failed'] = $errors;
@ -97,8 +99,6 @@ function install_content(App $a) {
$wizard_status = "";
$install_title = L10n::t('Friendica Communications Server - Setup');
if (x($a->data, 'db_conn_failed')) {
$install_wizard_pass = 2;
$wizard_status = L10n::t('Could not connect to database.');
@ -125,13 +125,8 @@ function install_content(App $a) {
if (DBA::$connected) {
$r = q("SELECT COUNT(*) as `total` FROM `user`");
if (DBA::isResult($r) && $r[0]['total']) {
$tpl = get_markup_template('install.tpl');
return replace_macros($tpl, [
'$title' => $install_title,
'$pass' => '',
'$status' => L10n::t('Database already in use.'),
'$text' => '',
]);
$install_wizard_pass = 2;
$wizard_status = L10n::t('Database already in use.');
}
}
@ -153,19 +148,21 @@ function install_content(App $a) {
$phpath = defaults($_POST, 'phpath', 'php');
list($checks, $checkspassed) = Install::check($phpath);
$install = new Install($phpath);
$status = $install->checkAll($a->getBasePath(), $a->getBaseURL());
$tpl = get_markup_template('install_checks.tpl');
$o .= replace_macros($tpl, [
'$title' => $install_title,
'$pass' => L10n::t('System check'),
'$checks' => $checks,
'$passed' => $checkspassed,
'$checks' => $install->getChecks(),
'$passed' => $status,
'$see_install' => L10n::t('Please see the file "INSTALL.txt".'),
'$next' => L10n::t('Next'),
'$reload' => L10n::t('Check again'),
'$phpath' => $phpath,
'$baseurl' => System::baseUrl(),
'$baseurl' => $a->getBaseURL(),
]);
return $o;
}; break;
@ -197,7 +194,7 @@ function install_content(App $a) {
'$lbl_10' => L10n::t('Please select a default timezone for your website'),
'$baseurl' => System::baseUrl(),
'$baseurl' => $a->getBaseURL(),
'$phpath' => $phpath,
@ -235,9 +232,7 @@ function install_content(App $a) {
'$timezone' => Temporal::getTimezoneField('timezone', L10n::t('Please select a default timezone for your website'), $timezone, ''),
'$language' => ['language', L10n::t('System Language:'), 'en', L10n::t('Set the default language for your Friendica installation interface and to send emails.'), $lang_choices],
'$baseurl' => System::baseUrl(),
'$baseurl' => $a->getBaseURL(),
'$submit' => L10n::t('Submit'),

View file

@ -58,14 +58,9 @@ function invite_post(App $a)
}
if ($invitation_only && ($invites_remaining || is_site_admin())) {
$code = autoname(8) . srand(1000, 9999);
$code = Friendica\Model\Register::createForInvitation();
$nmessage = str_replace('$invite_code', $code, $message);
$r = q("INSERT INTO `register` (`hash`,`created`) VALUES ('%s', '%s') ",
DBA::escape($code),
DBA::escape(DateTimeFormat::utcNow())
);
if (! is_site_admin()) {
$invites_remaining --;
if ($invites_remaining >= 0) {

View file

@ -39,7 +39,7 @@ require_once 'include/items.php';
function item_post(App $a) {
if (!local_user() && !remote_user()) {
return;
return 0;
}
require_once 'include/security.php';
@ -154,12 +154,12 @@ function item_post(App $a) {
if (($message_id != '') && ($profile_uid != 0)) {
if (Item::exists(['uri' => $message_id, 'uid' => $profile_uid])) {
logger("Message with URI ".$message_id." already exists for user ".$profile_uid, LOGGER_DEBUG);
return;
return 0;
}
}
// 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::OSTATUS, Protocol::DIASPORA, Protocol::DFRN]);
$allow_comment = local_user() && ($profile_uid == 0) && $parent && in_array($parent_item['network'], [Protocol::ACTIVITYPUB, Protocol::OSTATUS, Protocol::DIASPORA, Protocol::DFRN]);
// Now check that valid personal details have been provided
if (!can_write_wall($profile_uid) && !$allow_comment) {
@ -183,7 +183,7 @@ function item_post(App $a) {
$user = DBA::selectFirst('user', [], ['uid' => $profile_uid]);
if (!DBA::isResult($user) && !$parent) {
return;
return 0;
}
$categories = '';
@ -240,7 +240,7 @@ function item_post(App $a) {
$emailcc = notags(trim(defaults($_REQUEST, 'emailcc' , '')));
$body = escape_tags(trim(defaults($_REQUEST, 'body' , '')));
$network = notags(trim(defaults($_REQUEST, 'network' , Protocol::DFRN)));
$guid = System::createGUID(32);
$guid = System::createUUID();
$postopts = defaults($_REQUEST, 'postopts', '');
@ -343,20 +343,11 @@ function item_post(App $a) {
$tags = get_tags($body);
// Add a tag if the parent contact is from OStatus (This will notify them during delivery)
if ($parent) {
if ($thr_parent_contact['network'] == Protocol::OSTATUS) {
$contact = '@[url=' . $thr_parent_contact['url'] . ']' . $thr_parent_contact['nick'] . '[/url]';
if (!stripos(implode($tags), '[url=' . $thr_parent_contact['url'] . ']')) {
$tags[] = $contact;
}
}
if ($parent_contact['network'] == Protocol::OSTATUS) {
$contact = '@[url=' . $parent_contact['url'] . ']' . $parent_contact['nick'] . '[/url]';
if (!stripos(implode($tags), '[url=' . $parent_contact['url'] . ']')) {
$tags[] = $contact;
}
// Add a tag if the parent contact is from ActivityPub or OStatus (This will notify them)
if ($parent && in_array($thr_parent_contact['network'], [Protocol::OSTATUS, Protocol::ACTIVITYPUB])) {
$contact = '@[url=' . $thr_parent_contact['url'] . ']' . $thr_parent_contact['nick'] . '[/url]';
if (!stripos(implode($tags), '[url=' . $thr_parent_contact['url'] . ']')) {
$tags[] = $contact;
}
}
@ -843,6 +834,10 @@ function item_post(App $a) {
logger('post_complete');
if ($api_source) {
return $post_id;
}
item_post_return(System::baseUrl(), $api_source, $return_path);
// NOTREACHED
}
@ -881,13 +876,13 @@ function item_content(App $a)
$o = '';
if (($a->argc == 3) && ($a->argv[1] === 'drop') && intval($a->argv[2])) {
if (is_ajax()) {
if ($a->isAjax()) {
$o = Item::deleteForUser(['id' => $a->argv[2]], local_user());
} else {
$o = drop_item($a->argv[2]);
}
if (is_ajax()) {
if ($a->isAjax()) {
// ajax return: [<item id>, 0 (no perm) | <owner id>]
echo json_encode([intval($a->argv[2]), intval($o)]);
killme();
@ -1020,12 +1015,7 @@ function handle_tag(App $a, &$body, &$inform, &$str_tags, $profile_uid, $tag, $n
$profile = $contact["url"];
$alias = $contact["alias"];
$newname = $contact["nick"];
if (($newname == "") || (($contact["network"] != Protocol::OSTATUS) && ($contact["network"] != Protocol::TWITTER)
&& ($contact["network"] != Protocol::STATUSNET))) {
$newname = $contact["name"];
}
$newname = defaults($contact, "name", $contact["nick"]);
}
//if there is an url for this persons profile

View file

@ -8,27 +8,31 @@ use Friendica\Core\L10n;
use Friendica\Database\DBA;
use Friendica\Model\Item;
function lockview_content(App $a) {
function lockview_content(App $a)
{
$type = (($a->argc > 1) ? $a->argv[1] : 0);
if (is_numeric($type)) {
$item_id = intval($type);
$type='item';
$type = 'item';
} else {
$item_id = (($a->argc > 2) ? intval($a->argv[2]) : 0);
}
if (!$item_id)
if (!$item_id) {
killme();
}
if (!in_array($type, ['item','photo','event']))
if (!in_array($type, ['item','photo','event'])) {
killme();
}
$fields = ['uid', 'private', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid'];
$fields = ['uid', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid'];
$condition = ['id' => $item_id];
if ($type != 'item') {
$item = DBA::selectFirst($type, $fields, $condition);
} else {
$fields[] = 'private';
$item = Item::selectFirst($fields, $condition);
}
@ -43,18 +47,21 @@ function lockview_content(App $a) {
killme();
}
if (($item['private'] == 1) && empty($item['allow_cid']) && empty($item['allow_gid'])
&& empty($item['deny_cid']) && empty($item['deny_gid'])) {
if (isset($item['private'])
&& $item['private'] == 1
&& empty($item['allow_cid'])
&& empty($item['allow_gid'])
&& empty($item['deny_cid'])
&& empty($item['deny_gid']))
{
echo L10n::t('Remote privacy information not available.') . '<br />';
killme();
}
$allowed_users = expand_acl($item['allow_cid']);
$allowed_users = expand_acl($item['allow_cid']);
$allowed_groups = expand_acl($item['allow_gid']);
$deny_users = expand_acl($item['deny_cid']);
$deny_groups = expand_acl($item['deny_gid']);
$deny_users = expand_acl($item['deny_cid']);
$deny_groups = expand_acl($item['deny_gid']);
$o = L10n::t('Visible to:') . '<br />';
$l = [];
@ -63,36 +70,44 @@ function lockview_content(App $a) {
$r = q("SELECT `name` FROM `group` WHERE `id` IN ( %s )",
DBA::escape(implode(', ', $allowed_groups))
);
if (DBA::isResult($r))
foreach($r as $rr)
if (DBA::isResult($r)) {
foreach ($r as $rr) {
$l[] = '<b>' . $rr['name'] . '</b>';
}
}
}
if (count($allowed_users)) {
$r = q("SELECT `name` FROM `contact` WHERE `id` IN ( %s )",
DBA::escape(implode(', ',$allowed_users))
DBA::escape(implode(', ', $allowed_users))
);
if (DBA::isResult($r))
foreach($r as $rr)
if (DBA::isResult($r)) {
foreach ($r as $rr) {
$l[] = $rr['name'];
}
}
}
if (count($deny_groups)) {
$r = q("SELECT `name` FROM `group` WHERE `id` IN ( %s )",
DBA::escape(implode(', ', $deny_groups))
);
if (DBA::isResult($r))
foreach($r as $rr)
if (DBA::isResult($r)) {
foreach ($r as $rr) {
$l[] = '<b><strike>' . $rr['name'] . '</strike></b>';
}
}
}
if (count($deny_users)) {
$r = q("SELECT `name` FROM `contact` WHERE `id` IN ( %s )",
DBA::escape(implode(', ',$deny_users))
DBA::escape(implode(', ', $deny_users))
);
if (DBA::isResult($r))
foreach($r as $rr)
if (DBA::isResult($r)) {
foreach ($r as $rr) {
$l[] = '<strike>' . $rr['name'] . '</strike>';
}
}
}
echo $o . implode(', ', $l);

View file

@ -59,16 +59,16 @@ function match_content(App $a)
}
if (strlen(Config::get('system', 'directory'))) {
$x = Network::post(get_server().'/msearch', $params);
$x = Network::post(get_server().'/msearch', $params)->getBody();
} else {
$x = Network::post(System::baseUrl() . '/msearch', $params);
$x = Network::post(System::baseUrl() . '/msearch', $params)->getBody();
}
$j = json_decode($x);
if ($j->total) {
$a->set_pager_total($j->total);
$a->set_pager_itemspage($j->items_page);
$a->setPagerTotal($j->total);
$a->setPagerItemsPage($j->items_page);
}
if (count($j->results)) {

View file

@ -16,6 +16,7 @@ use Friendica\Model\Mail;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Proxy as ProxyUtils;
use Friendica\Util\Temporal;
use Friendica\Module\Login;
require_once 'include/conversation.php';
@ -46,12 +47,6 @@ function message_init(App $a)
'$baseurl' => System::baseUrl(true),
'$base' => $base
]);
$end_tpl = get_markup_template('message-end.tpl');
$a->page['end'] .= replace_macros($end_tpl, [
'$baseurl' => System::baseUrl(true),
'$base' => $base
]);
}
function message_post(App $a)
@ -92,7 +87,7 @@ function message_post(App $a)
$a->argc = 2;
$a->argv[1] = 'new';
} else {
goaway($_SESSION['return_url']);
goaway($a->cmd . '/' . $ret);
}
}
@ -103,7 +98,7 @@ function message_content(App $a)
if (!local_user()) {
notice(L10n::t('Permission denied.') . EOL);
return;
return Login::form();
}
$myprofile = System::baseUrl() . '/profile/' . $a->user['nickname'];
@ -160,17 +155,28 @@ function message_content(App $a)
// Now check how the user responded to the confirmation query
if (!empty($_REQUEST['canceled'])) {
goaway($_SESSION['return_url']);
goaway('/message');
}
$cmd = $a->argv[1];
if ($cmd === 'drop') {
$message = DBA::selectFirst('mail', ['convid'], ['id' => $a->argv[2], 'uid' => local_user()]);
if(!DBA::isResult($message)){
info(L10n::t('Conversation not found.') . EOL);
goaway('/message');
}
if (DBA::delete('mail', ['id' => $a->argv[2], 'uid' => local_user()])) {
info(L10n::t('Message deleted.') . EOL);
}
//goaway(System::baseUrl(true) . '/message' );
goaway($_SESSION['return_url']);
$conversation = DBA::selectFirst('mail', ['id'], ['convid' => $message['convid'], 'uid' => local_user()]);
if(!DBA::isResult($conversation)){
info(L10n::t('Conversation removed.') . EOL);
goaway('/message');
}
goaway('/message/' . $conversation['id'] );
} else {
$r = q("SELECT `parent-uri`,`convid` FROM `mail` WHERE `id` = %d AND `uid` = %d LIMIT 1",
intval($a->argv[2]),
@ -184,8 +190,7 @@ function message_content(App $a)
info(L10n::t('Conversation removed.') . EOL);
}
}
//goaway(System::baseUrl(true) . '/message' );
goaway($_SESSION['return_url']);
goaway('/message' );
}
}
@ -199,13 +204,6 @@ function message_content(App $a)
'$linkurl' => L10n::t('Please enter a link URL:')
]);
$tpl = get_markup_template('msg-end.tpl');
$a->page['end'] .= replace_macros($tpl, [
'$baseurl' => System::baseUrl(true),
'$nickname' => $a->user['nickname'],
'$linkurl' => L10n::t('Please enter a link URL:')
]);
$preselect = isset($a->argv[2]) ? [$a->argv[2]] : [];
$prename = $preurl = $preid = '';
@ -281,7 +279,7 @@ function message_content(App $a)
);
if (DBA::isResult($r)) {
$a->set_pager_total($r[0]['total']);
$a->setPagerTotal($r[0]['total']);
}
$r = get_messages(local_user(), $a->pager['start'], $a->pager['itemspage']);
@ -344,13 +342,6 @@ function message_content(App $a)
'$linkurl' => L10n::t('Please enter a link URL:')
]);
$tpl = get_markup_template('msg-end.tpl');
$a->page['end'] .= replace_macros($tpl, [
'$baseurl' => System::baseUrl(true),
'$nickname' => $a->user['nickname'],
'$linkurl' => L10n::t('Please enter a link URL:')
]);
$mails = [];
$seen = 0;
$unknown = false;
@ -488,7 +479,7 @@ function render_messages(array $msg, $t)
'$id' => $rr['id'],
'$from_name' => $participants,
'$from_url' => Contact::magicLink($rr['url']),
'$from_addr' => $contact['addr'],
'$from_addr' => defaults($contact, 'addr', ''),
'$sparkle' => ' sparkle',
'$from_photo' => ProxyUtils::proxifyUrl($from_photo, false, ProxyUtils::SIZE_THUMB),
'$subject' => $subject_e,

View file

@ -302,7 +302,7 @@ function networkPager($a, $update)
$itemspage_network = $a->force_max_items;
}
$a->set_pager_itemspage($itemspage_network);
$a->setPagerItemsPage($itemspage_network);
return sprintf(" LIMIT %d, %d ", intval($a->pager['start']), intval($a->pager['itemspage']));
}
@ -721,7 +721,7 @@ function networkThreadedView(App $a, $update, $parent)
if ($last_received != '') {
$last_date = $last_received;
$sql_range .= sprintf(" AND $sql_table.`received` < '%s'", DBA::escape($last_received));
$a->set_pager_page(1);
$a->setPagerPage(1);
$pager_sql = sprintf(" LIMIT %d, %d ", intval($a->pager['start']), intval($a->pager['itemspage']));
}
break;
@ -729,7 +729,7 @@ function networkThreadedView(App $a, $update, $parent)
if ($last_commented != '') {
$last_date = $last_commented;
$sql_range .= sprintf(" AND $sql_table.`commented` < '%s'", DBA::escape($last_commented));
$a->set_pager_page(1);
$a->setPagerPage(1);
$pager_sql = sprintf(" LIMIT %d, %d ", intval($a->pager['start']), intval($a->pager['itemspage']));
}
break;
@ -737,14 +737,14 @@ function networkThreadedView(App $a, $update, $parent)
if ($last_created != '') {
$last_date = $last_created;
$sql_range .= sprintf(" AND $sql_table.`created` < '%s'", DBA::escape($last_created));
$a->set_pager_page(1);
$a->setPagerPage(1);
$pager_sql = sprintf(" LIMIT %d, %d ", intval($a->pager['start']), intval($a->pager['itemspage']));
}
break;
case 'id':
if (($last_id > 0) && ($sql_table == '`thread`')) {
$sql_range .= sprintf(" AND $sql_table.`iid` < '%s'", DBA::escape($last_id));
$a->set_pager_page(1);
$a->setPagerPage(1);
$pager_sql = sprintf(" LIMIT %d, %d ", intval($a->pager['start']), intval($a->pager['itemspage']));
}
break;
@ -810,7 +810,7 @@ function networkThreadedView(App $a, $update, $parent)
}
// Only show it when unfiltered (no groups, no networks, ...)
if (in_array($nets, ['', Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS]) && (strlen($sql_extra . $sql_extra2 . $sql_extra3) == 0)) {
if (in_array($nets, ['', Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS]) && (strlen($sql_extra . $sql_extra2 . $sql_extra3) == 0)) {
if (DBA::isResult($r)) {
$top_limit = current($r)['order_date'];
$bottom_limit = end($r)['order_date'];

View file

@ -36,13 +36,13 @@ function newmember_content(App $a)
$o .= '<li>' . '<a target="newmember" href="settings/connectors">' . L10n::t('Importing Emails') . '</a><br />' . L10n::t('Enter your email access information on your Connector Settings page if you wish to import and interact with friends or mailing lists from your email INBOX') . '</li>' . EOL;
}
$o .= '<li>' . '<a target="newmember" href="contacts">' . L10n::t('Go to Your Contacts Page') . '</a><br />' . L10n::t('Your Contacts page is your gateway to managing friendships and connecting with friends on other networks. Typically you enter their address or site URL in the <em>Add New Contact</em> dialog.') . '</li>' . EOL;
$o .= '<li>' . '<a target="newmember" href="contact">' . L10n::t('Go to Your Contacts Page') . '</a><br />' . L10n::t('Your Contacts page is your gateway to managing friendships and connecting with friends on other networks. Typically you enter their address or site URL in the <em>Add New Contact</em> dialog.') . '</li>' . EOL;
$o .= '<li>' . '<a target="newmember" href="directory">' . L10n::t("Go to Your Site's Directory") . '</a><br />' . L10n::t('The Directory page lets you find other people in this network or other federated sites. Look for a <em>Connect</em> or <em>Follow</em> link on their profile page. Provide your own Identity Address if requested.') . '</li>' . EOL;
$o .= '<li>' . '<a target="newmember" href="contacts">' . L10n::t('Finding New People') . '</a><br />' . L10n::t("On the side panel of the Contacts page are several tools to find new friends. We can match people by interest, look up people by name or interest, and provide suggestions based on network relationships. On a brand new site, friend suggestions will usually begin to be populated within 24 hours.") . '</li>' . EOL;
$o .= '<li>' . '<a target="newmember" href="contact">' . L10n::t('Finding New People') . '</a><br />' . L10n::t("On the side panel of the Contacts page are several tools to find new friends. We can match people by interest, look up people by name or interest, and provide suggestions based on network relationships. On a brand new site, friend suggestions will usually begin to be populated within 24 hours.") . '</li>' . EOL;
$o .= '</ul>';
$o .= '<h4>' . L10n::t('Groups') . '</h4>';
$o .= '<ul>';
$o .= '<li>' . '<a target="newmember" href="contacts">' . L10n::t('Group Your Contacts') . '</a><br />' . L10n::t('Once you have made some friends, organize them into private conversation groups from the sidebar of your Contacts page and then you can interact with each group privately on your Network page.') . '</li>' . EOL;
$o .= '<li>' . '<a target="newmember" href="contact">' . L10n::t('Group Your Contacts') . '</a><br />' . L10n::t('Once you have made some friends, organize them into private conversation groups from the sidebar of your Contacts page and then you can interact with each group privately on your Network page.') . '</li>' . EOL;
if (Config::get('system', 'newuser_private')) {
$o .= '<li>' . '<a target="newmember" href="help/Groups-and-Privacy">' . L10n::t("Why Aren't My Posts Public?") . '</a><br />' . L10n::t("Friendica respects your privacy. By default, your posts will only show up to people you've added as friends. For more information, see the help section from the link above.") . '</li>' . EOL;

View file

@ -215,7 +215,7 @@ function nodeinfo_cron() {
logger('local_comments: ' . $local_comments, LOGGER_DEBUG);
// Now trying to register
$url = 'http://the-federation.info/register/'.$a->get_hostname();
$url = 'http://the-federation.info/register/'.$a->getHostName();
logger('registering url: '.$url, LOGGER_DEBUG);
$ret = Network::fetchUrl($url);
logger('registering answer: '.$ret, LOGGER_DEBUG);

View file

@ -61,7 +61,7 @@ function notes_content(App $a, $update = false)
$condition = ['uid' => local_user(), 'post-type' => Item::PT_PERSONAL_NOTE, 'gravity' => GRAVITY_PARENT,
'wall' => false, 'contact-id'=> $a->contact['id']];
$a->set_pager_itemspage(40);
$a->setPagerItemsPage(40);
$params = ['order' => ['created' => true],
'limit' => [$a->pager['start'], $a->pager['itemspage']]];
@ -70,8 +70,11 @@ function notes_content(App $a, $update = false)
$count = 0;
if (DBA::isResult($r)) {
$count = count($r);
$o .= conversation($a, DBA::toArray($r), 'notes', $update);
$notes = DBA::toArray($r);
$count = count($notes);
$o .= conversation($a, $notes, 'notes', $update);
}
$o .= alt_pager($a, $count);

View file

@ -12,6 +12,7 @@ use Friendica\Core\NotificationsManager;
use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Module\Login;
function notifications_post(App $a)
{
@ -21,7 +22,7 @@ function notifications_post(App $a)
$request_id = (($a->argc > 1) ? $a->argv[1] : 0);
if ($request_id === "all") {
if ($request_id === 'all') {
return;
}
@ -65,11 +66,11 @@ function notifications_content(App $a)
{
if (!local_user()) {
notice(L10n::t('Permission denied.') . EOL);
return;
return Login::form();
}
$page = (x($_REQUEST,'page') ? $_REQUEST['page'] : 1);
$show = (x($_REQUEST,'show') ? $_REQUEST['show'] : 0);
$page = defaults($_REQUEST, 'page', 1);
$show = defaults($_REQUEST, 'show', 0);
Nav::setSelected('notifications');
@ -87,10 +88,11 @@ function notifications_content(App $a)
$perpage = 20;
$startrec = ($page * $perpage) - $perpage;
$notif_header = L10n::t('Notifications');
// Get introductions
if ((($a->argc > 1) && ($a->argv[1] == 'intros')) || (($a->argc == 1))) {
Nav::setSelected('introductions');
$notif_header = L10n::t('Notifications');
$all = (($a->argc > 2) && ($a->argv[2] == 'all'));
@ -115,12 +117,10 @@ function notifications_content(App $a)
} elseif (($a->argc > 1) && ($a->argv[1] == 'home')) {
$notif_header = L10n::t('Home Notifications');
$notifs = $nm->homeNotifs($show, $startrec, $perpage);
}
// Set the pager
$a->set_pager_itemspage($perpage);
$a->setPagerItemsPage($perpage);
// Add additional informations (needed for json output)
$notifs['items_page'] = $a->pager['itemspage'];
@ -133,14 +133,15 @@ function notifications_content(App $a)
$notif_tpl = get_markup_template('notifications.tpl');
if (!isset($notifs['ident'])) {
logger('Missing data in notifs: ' . System::callstack(20), LOGGER_DEBUG);
}
$notif_show_lnk = [
'href' => ($show ? 'notifications/' . $notifs['ident'] : 'notifications/' . $notifs['ident'] . '?show=all' ),
'text' => ($show ? L10n::t('Show unread') : L10n::t('Show all')),
];
// Process the data for template creation
if ($notifs['ident'] === 'introductions') {
if (defaults($notifs, 'ident', '') === 'introductions') {
$sugg = get_markup_template('suggestions.tpl');
$tpl = get_markup_template("intros.tpl");
$tpl = get_markup_template('intros.tpl');
// The link to switch between ignored and normal connection requests
$notif_show_lnk = [
@ -150,127 +151,121 @@ function notifications_content(App $a)
// Loop through all introduction notifications.This creates an array with the output html for each
// introduction
foreach ($notifs['notifications'] as $it) {
foreach ($notifs['notifications'] as $notif) {
// There are two kind of introduction. Contacts suggested by other contacts and normal connection requests.
// We have to distinguish between these two because they use different data.
switch ($it['label']) {
switch ($notif['label']) {
case 'friend_suggestion':
$notif_content[] = replace_macros($sugg, [
'$type' => $it['label'],
'$type' => $notif['label'],
'$str_notifytype' => L10n::t('Notification type:'),
'$notify_type' => $it['notify_type'],
'$intro_id' => $it['intro_id'],
'$notify_type'=> $notif['notify_type'],
'$intro_id' => $notif['intro_id'],
'$lbl_madeby' => L10n::t('Suggested by:'),
'$madeby' => $it['madeby'],
'$madeby_url' => $it['madeby_url'],
'$madeby_zrl' => $it['madeby_zrl'],
'$madeby_addr' => $it['madeby_addr'],
'$contact_id' => $it['contact_id'],
'$photo' => $it['photo'],
'$fullname' => $it['name'],
'$url' => $it['url'],
'$zrl' => $it['zrl'],
'$lbl_url' => L10n::t('Profile URL'),
'$addr' => $it['addr'],
'$hidden' => ['hidden', L10n::t('Hide this contact from others'), ($it['hidden'] == 1), ''],
'$knowyou' => $it['knowyou'],
'$approve' => L10n::t('Approve'),
'$note' => $it['note'],
'$request' => $it['request'],
'$ignore' => L10n::t('Ignore'),
'$discard' => L10n::t('Discard'),
'$madeby' => $notif['madeby'],
'$madeby_url' => $notif['madeby_url'],
'$madeby_zrl' => $notif['madeby_zrl'],
'$madeby_addr'=> $notif['madeby_addr'],
'$contact_id' => $notif['contact_id'],
'$photo' => $notif['photo'],
'$fullname' => $notif['name'],
'$url' => $notif['url'],
'$zrl' => $notif['zrl'],
'$lbl_url' => L10n::t('Profile URL'),
'$addr' => $notif['addr'],
'$hidden' => ['hidden', L10n::t('Hide this contact from others'), ($notif['hidden'] == 1), ''],
'$knowyou' => $notif['knowyou'],
'$approve' => L10n::t('Approve'),
'$note' => $notif['note'],
'$request' => $notif['request'],
'$ignore' => L10n::t('Ignore'),
'$discard' => L10n::t('Discard'),
]);
break;
// Normal connection requests
default:
$friend_selected = (($it['network'] !== Protocol::OSTATUS) ? ' checked="checked" ' : ' disabled ');
$fan_selected = (($it['network'] === Protocol::OSTATUS) ? ' checked="checked" disabled ' : '');
$dfrn_tpl = get_markup_template('netfriend.tpl');
$friend_selected = (($notif['network'] !== Protocol::OSTATUS) ? ' checked="checked" ' : ' disabled ');
$fan_selected = (($notif['network'] === Protocol::OSTATUS) ? ' checked="checked" disabled ' : '');
$knowyou = '';
$lbl_knowyou = '';
$dfrn_text = '';
$helptext = '';
$helptext2 = '';
$helptext3 = '';
$knowyou = '';
$helptext = '';
$helptext2 = '';
$helptext3 = '';
if ($it['network'] === Protocol::DFRN || $it['network'] === Protocol::DIASPORA) {
if ($it['network'] === Protocol::DFRN) {
$lbl_knowyou = L10n::t('Claims to be known to you: ');
$knowyou = (($it['knowyou']) ? L10n::t('yes') : L10n::t('no'));
$helptext = L10n::t('Shall your connection be bidirectional or not?');
$helptext2 = L10n::t('Accepting %s as a friend allows %s to subscribe to your posts, and you will also receive updates from them in your news feed.', $it['name'], $it['name']);
$helptext3 = L10n::t('Accepting %s as a subscriber allows them to subscribe to your posts, but you will not receive updates from them in your news feed.', $it['name']);
} else {
$knowyou = '';
$helptext = L10n::t('Shall your connection be bidirectional or not?');
$helptext2 = L10n::t('Accepting %s as a friend allows %s to subscribe to your posts, and you will also receive updates from them in your news feed.', $it['name'], $it['name']);
$helptext3 = L10n::t('Accepting %s as a sharer allows them to subscribe to your posts, but you will not receive updates from them in your news feed.', $it['name']);
}
if ($notif['network'] === Protocol::DFRN) {
$lbl_knowyou = L10n::t('Claims to be known to you: ');
$knowyou = (($notif['knowyou']) ? L10n::t('yes') : L10n::t('no'));
$helptext = L10n::t('Shall your connection be bidirectional or not?');
$helptext2 = L10n::t('Accepting %s as a friend allows %s to subscribe to your posts, and you will also receive updates from them in your news feed.', $notif['name'], $notif['name']);
$helptext3 = L10n::t('Accepting %s as a subscriber allows them to subscribe to your posts, but you will not receive updates from them in your news feed.', $notif['name']);
} elseif ($notif['network'] === Protocol::DIASPORA) {
$helptext = L10n::t('Shall your connection be bidirectional or not?');
$helptext2 = L10n::t('Accepting %s as a friend allows %s to subscribe to your posts, and you will also receive updates from them in your news feed.', $notif['name'], $notif['name']);
$helptext3 = L10n::t('Accepting %s as a sharer allows them to subscribe to your posts, but you will not receive updates from them in your news feed.', $notif['name']);
}
$dfrn_text = replace_macros($dfrn_tpl,[
'$intro_id' => $it['intro_id'],
$dfrn_tpl = get_markup_template('netfriend.tpl');
$dfrn_text = replace_macros($dfrn_tpl, [
'$intro_id' => $notif['intro_id'],
'$friend_selected' => $friend_selected,
'$fan_selected' => $fan_selected,
'$fan_selected'=> $fan_selected,
'$approve_as1' => $helptext,
'$approve_as2' => $helptext2,
'$approve_as3' => $helptext3,
'$as_friend' => L10n::t('Friend'),
'$as_fan' => (($it['network'] == Protocol::DIASPORA) ? L10n::t('Sharer') : L10n::t('Subscriber'))
'$as_friend' => L10n::t('Friend'),
'$as_fan' => (($notif['network'] == Protocol::DIASPORA) ? L10n::t('Sharer') : L10n::t('Subscriber'))
]);
$header = $it["name"];
$header = $notif['name'];
if ($it["addr"] != "") {
$header .= " <".$it["addr"].">";
if ($notif['addr'] != '') {
$header .= ' <' . $notif['addr'] . '>';
}
$header .= " (".ContactSelector::networkToName($it['network'], $it['url']).")";
$header .= ' (' . ContactSelector::networkToName($notif['network'], $notif['url']) . ')';
if ($it['network'] != Protocol::DIASPORA) {
if ($notif['network'] != Protocol::DIASPORA) {
$discard = L10n::t('Discard');
} else {
$discard = '';
}
$notif_content[] = replace_macros($tpl, [
'$type' => $it['label'],
'$header' => htmlentities($header),
'$type' => $notif['label'],
'$header' => htmlentities($header),
'$str_notifytype' => L10n::t('Notification type:'),
'$notify_type' => $it['notify_type'],
'$dfrn_text' => $dfrn_text,
'$dfrn_id' => $it['dfrn_id'],
'$uid' => $it['uid'],
'$intro_id' => $it['intro_id'],
'$contact_id' => $it['contact_id'],
'$photo' => $it['photo'],
'$fullname' => $it['name'],
'$location' => $it['location'],
'$lbl_location' => L10n::t('Location:'),
'$about' => $it['about'],
'$lbl_about' => L10n::t('About:'),
'$keywords' => $it['keywords'],
'$lbl_keywords' => L10n::t('Tags:'),
'$gender' => $it['gender'],
'$lbl_gender' => L10n::t('Gender:'),
'$hidden' => ['hidden', L10n::t('Hide this contact from others'), ($it['hidden'] == 1), ''],
'$url' => $it['url'],
'$zrl' => $it['zrl'],
'$lbl_url' => L10n::t('Profile URL'),
'$addr' => $it['addr'],
'$notify_type' => $notif['notify_type'],
'$dfrn_text' => $dfrn_text,
'$dfrn_id' => $notif['dfrn_id'],
'$uid' => $notif['uid'],
'$intro_id' => $notif['intro_id'],
'$contact_id' => $notif['contact_id'],
'$photo' => $notif['photo'],
'$fullname' => $notif['name'],
'$location' => $notif['location'],
'$lbl_location'=> L10n::t('Location:'),
'$about' => $notif['about'],
'$lbl_about' => L10n::t('About:'),
'$keywords' => $notif['keywords'],
'$lbl_keywords'=> L10n::t('Tags:'),
'$gender' => $notif['gender'],
'$lbl_gender' => L10n::t('Gender:'),
'$hidden' => ['hidden', L10n::t('Hide this contact from others'), ($notif['hidden'] == 1), ''],
'$url' => $notif['url'],
'$zrl' => $notif['zrl'],
'$lbl_url' => L10n::t('Profile URL'),
'$addr' => $notif['addr'],
'$lbl_knowyou' => $lbl_knowyou,
'$lbl_network' => L10n::t('Network:'),
'$network' => ContactSelector::networkToName($it['network'], $it['url']),
'$knowyou' => $knowyou,
'$approve' => L10n::t('Approve'),
'$note' => $it['note'],
'$ignore' => L10n::t('Ignore'),
'$discard' => $discard,
'$network' => ContactSelector::networkToName($notif['network'], $notif['url']),
'$knowyou' => $knowyou,
'$approve' => L10n::t('Approve'),
'$note' => $notif['note'],
'$ignore' => L10n::t('Ignore'),
'$discard' => $discard,
]);
break;
}
@ -280,57 +275,47 @@ function notifications_content(App $a)
info(L10n::t('No introductions.') . EOL);
}
// Normal notifications (no introductions)
} else {
// The template files we need in different cases for formatting the content
$tpl_item_like = 'notifications_likes_item.tpl';
$tpl_item_dislike = 'notifications_dislikes_item.tpl';
$tpl_item_attend = 'notifications_attend_item.tpl';
$tpl_item_attendno = 'notifications_attend_item.tpl';
$tpl_item_attendmaybe = 'notifications_attend_item.tpl';
$tpl_item_friend = 'notifications_friends_item.tpl';
$tpl_item_comment = 'notifications_comments_item.tpl';
$tpl_item_post = 'notifications_posts_item.tpl';
$tpl_item_notify = 'notify.tpl';
// Normal notifications (no introductions)
} elseif (!empty($notifs['notifications'])) {
// Loop trough ever notification This creates an array with the output html for each
// notification and apply the correct template according to the notificationtype (label).
foreach ($notifs['notifications'] as $it) {
foreach ($notifs['notifications'] as $notif) {
$notification_templates = [
'like' => 'notifications_likes_item.tpl',
'dislike' => 'notifications_dislikes_item.tpl',
'attend' => 'notifications_attend_item.tpl',
'attendno' => 'notifications_attend_item.tpl',
'attendmaybe' => 'notifications_attend_item.tpl',
'friend' => 'notifications_friends_item.tpl',
'comment' => 'notifications_comments_item.tpl',
'post' => 'notifications_posts_item.tpl',
'notify' => 'notify.tpl',
];
// We use the notification label to get the correct template file
$tpl_var_name = 'tpl_item_'.$it['label'];
$tpl_notif = get_markup_template($$tpl_var_name);
$tpl_notif = get_markup_template($notification_templates[$notif['label']]);
$notif_content[] = replace_macros($tpl_notif,[
'$item_label' => $it['label'],
'$item_link' => $it['link'],
'$item_image' => $it['image'],
'$item_url' => $it['url'],
'$item_text' => $it['text'],
'$item_when' => $it['when'],
'$item_ago' => $it['ago'],
'$item_seen' => $it['seen'],
$notif_content[] = replace_macros($tpl_notif, [
'$item_label' => $notif['label'],
'$item_link' => $notif['link'],
'$item_image' => $notif['image'],
'$item_url' => $notif['url'],
'$item_text' => $notif['text'],
'$item_when' => $notif['when'],
'$item_ago' => $notif['ago'],
'$item_seen' => $notif['seen'],
]);
}
$notif_show_lnk = [
'href' => ($show ? 'notifications/'.$notifs['ident'] : 'notifications/'.$notifs['ident'].'?show=all' ),
'text' => ($show ? L10n::t('Show unread') : L10n::t('Show all')),
];
// Output if there aren't any notifications available
if (count($notifs['notifications']) == 0) {
$notif_nocontent = L10n::t('No more %s notifications.', $notifs['ident']);
}
} else {
$notif_nocontent = L10n::t('No more %s notifications.', $notifs['ident']);
}
$o .= replace_macros($notif_tpl, [
'$notif_header' => $notif_header,
'$tabs' => $tabs,
'$notif_content' => $notif_content,
'$notif_header' => $notif_header,
'$tabs' => $tabs,
'$notif_content' => $notif_content,
'$notif_nocontent' => $notif_nocontent,
'$notif_show_lnk' => $notif_show_lnk,
'$notif_paginate' => alt_pager($a, count($notif_content))
'$notif_show_lnk' => $notif_show_lnk,
'$notif_paginate' => alt_pager($a, count($notif_content))
]);
return $o;

View file

@ -27,7 +27,7 @@ function notify_init(App $a)
$nm->setSeen($note);
// The friendica client has problems with the GUID. this is some workaround
if ($a->is_friendica_app()) {
if ($a->isFriendicaApp()) {
require_once("include/items.php");
$urldata = parse_url($note['link']);
$guid = basename($urldata["path"]);

View file

@ -19,7 +19,7 @@ function openid_content(App $a) {
if((x($_GET,'openid_mode')) && (x($_SESSION,'openid'))) {
$openid = new LightOpenID($a->get_hostname());
$openid = new LightOpenID($a->getHostName());
if($openid->validate()) {

View file

@ -11,7 +11,7 @@ function opensearch_content(App $a) {
$o = replace_macros($tpl, [
'$baseurl' => System::baseUrl(),
'$nodename' => $a->get_hostname(),
'$nodename' => $a->getHostName(),
]);
echo $o;

View file

@ -15,7 +15,7 @@ function ostatus_subscribe_content(App $a) {
if (! local_user()) {
notice(L10n::t('Permission denied.') . EOL);
goaway($_SESSION['return_url']);
goaway('/ostatus_subscribe');
// NOTREACHED
}
@ -44,14 +44,14 @@ function ostatus_subscribe_content(App $a) {
$api = $contact["baseurl"]."/api/";
// Fetching friends
$data = Network::curl($api."statuses/friends.json?screen_name=".$contact["nick"]);
$curlResult = Network::curl($api."statuses/friends.json?screen_name=".$contact["nick"]);
if (!$data["success"]) {
if (!$curlResult->isSuccess()) {
PConfig::delete($uid, "ostatus", "legacy_contact");
return $o.L10n::t("Couldn't fetch friends for contact.");
}
PConfig::set($uid, "ostatus", "legacy_friends", $data["body"]);
PConfig::set($uid, "ostatus", "legacy_friends", $curlResult->getBody());
}
$friends = json_decode(PConfig::get($uid, "ostatus", "legacy_friends"));
@ -72,8 +72,8 @@ function ostatus_subscribe_content(App $a) {
$o .= "<p>".$counter."/".$total.": ".$url;
$data = Probe::uri($url);
if ($data["network"] == Protocol::OSTATUS) {
$curlResult = Probe::uri($url);
if ($curlResult["network"] == Protocol::OSTATUS) {
$result = Contact::createFromProbe($uid, $url, true, Protocol::OSTATUS);
if ($result["success"]) {
$o .= " - ".L10n::t("success");

View file

@ -8,128 +8,128 @@
* information and does format this information to BBCode
*
* @see ParseUrl::getSiteinfo() for more information about scraping embeddable content
*/
*/
use Friendica\App;
use Friendica\Core\Addon;
use Friendica\Util\Network;
use Friendica\Util\ParseUrl;
require_once("include/items.php");
function parse_url_content(App $a) {
require_once 'include/items.php';
function parse_url_content(App $a)
{
$text = null;
$str_tags = "";
$str_tags = '';
$br = "\n";
if (!empty($_GET["binurl"])) {
$url = trim(hex2bin($_GET["binurl"]));
if (!empty($_GET['binurl'])) {
$url = trim(hex2bin($_GET['binurl']));
} else {
$url = trim($_GET["url"]);
$url = trim($_GET['url']);
}
if (!empty($_GET["title"])) {
$title = strip_tags(trim($_GET["title"]));
if (!empty($_GET['title'])) {
$title = strip_tags(trim($_GET['title']));
}
if (!empty($_GET["description"])) {
$text = strip_tags(trim($_GET["description"]));
if (!empty($_GET['description'])) {
$text = strip_tags(trim($_GET['description']));
}
if (!empty($_GET["tags"])) {
$arr_tags = ParseUrl::convertTagsToArray($_GET["tags"]);
if (!empty($_GET['tags'])) {
$arr_tags = ParseUrl::convertTagsToArray($_GET['tags']);
if (count($arr_tags)) {
$str_tags = $br . implode(" ", $arr_tags) . $br;
$str_tags = $br . implode(' ', $arr_tags) . $br;
}
}
// Add url scheme if it is missing
$arrurl = parse_url($url);
if (!x($arrurl, "scheme")) {
if (x($arrurl, "host")) {
$url = "http:".$url;
if (!x($arrurl, 'scheme')) {
if (x($arrurl, 'host')) {
$url = 'http:' . $url;
} else {
$url = "http://".$url;
$url = 'http://' . $url;
}
}
logger("prse_url: " . $url);
logger($url);
// Check if the URL is an image, video or audio file. If so format
// the URL with the corresponding BBCode media tag
$redirects = 0;
// Fetch the header of the URL
$result = Network::curl($url, false, $redirects, ["novalidate" => true, "nobody" => true]);
if($result["success"]) {
$curlResponse = Network::curl($url, false, $redirects, ['novalidate' => true, 'nobody' => true]);
if ($curlResponse->isSuccess()) {
// Convert the header fields into an array
$hdrs = [];
$h = explode("\n", $result["header"]);
$h = explode("\n", $curlResponse->getHeader());
foreach ($h as $l) {
$header = array_map("trim", explode(":", trim($l), 2));
$header = array_map('trim', explode(':', trim($l), 2));
if (count($header) == 2) {
list($k,$v) = $header;
list($k, $v) = $header;
$hdrs[$k] = $v;
}
}
if (array_key_exists("Content-Type", $hdrs)) {
$type = $hdrs["Content-Type"];
$type = null;
if (array_key_exists('Content-Type', $hdrs)) {
$type = $hdrs['Content-Type'];
}
if ($type) {
if(stripos($type, "image/") !== false) {
echo $br . "[img]" . $url . "[/img]" . $br;
killme();
if (stripos($type, 'image/') !== false) {
echo $br . '[img]' . $url . '[/img]' . $br;
exit();
}
if (stripos($type, "video/") !== false) {
echo $br . "[video]" . $url . "[/video]" . $br;
killme();
if (stripos($type, 'video/') !== false) {
echo $br . '[video]' . $url . '[/video]' . $br;
exit();
}
if (stripos($type, "audio/") !== false) {
echo $br . "[audio]" . $url . "[/audio]" . $br;
killme();
if (stripos($type, 'audio/') !== false) {
echo $br . '[audio]' . $url . '[/audio]' . $br;
exit();
}
}
}
$template = "[bookmark=%s]%s[/bookmark]%s";
$template = '[bookmark=%s]%s[/bookmark]%s';
$arr = ["url" => $url, "text" => ""];
$arr = ['url' => $url, 'text' => ''];
Addon::callHooks("parse_link", $arr);
Addon::callHooks('parse_link', $arr);
if (strlen($arr["text"])) {
echo $arr["text"];
killme();
if (strlen($arr['text'])) {
echo $arr['text'];
exit();
}
// If there is already some content information submitted we don't
// need to parse the url for content.
if (!empty($url) && !empty($title) && !empty($text)) {
$title = str_replace(["\r", "\n"], ['', ''], $title);
$title = str_replace(["\r","\n"],["",""],$title);
$text = "[quote]" . trim($text) . "[/quote]" . $br;
$text = '[quote]' . trim($text) . '[/quote]' . $br;
$result = sprintf($template, $url, ($title) ? $title : $url, $text) . $str_tags;
logger("parse_url (unparsed): returns: " . $result);
logger('(unparsed): returns: ' . $result);
echo $result;
killme();
exit();
}
// Fetch the information directly from the webpage
$siteinfo = ParseUrl::getSiteinfo($url);
unset($siteinfo["keywords"]);
unset($siteinfo['keywords']);
// Format it as BBCode attachment
$info = add_page_info_data($siteinfo);
echo $info;
killme();
exit();
}
/**
@ -151,7 +151,8 @@ function parse_url_content(App $a) {
* @todo Remove this function after all Addons has been changed to use
* ParseUrl::getSiteinfoCached
*/
function parseurl_getsiteinfo_cached($url, $no_guessing = false, $do_oembed = true) {
function parseurl_getsiteinfo_cached($url, $no_guessing = false, $do_oembed = true)
{
$siteinfo = ParseUrl::getSiteinfoCached($url, $no_guessing, $do_oembed);
return $siteinfo;
}

View file

@ -192,7 +192,7 @@ function photo_init(App $a)
// If the photo is public and there is an existing photo directory store the photo there
if ($public and $file != '') {
// If the photo path isn't there, try to create it
$basepath = $a->get_basepath();
$basepath = $a->getBasePath();
if (!is_dir($basepath . "/photo")) {
if (is_writable($basepath)) {
mkdir($basepath . "/photo");

View file

@ -212,7 +212,7 @@ function photos_post(App $a)
}
// Check if the user has responded to a delete confirmation query
if ($_REQUEST['canceled']) {
if (!empty($_REQUEST['canceled'])) {
goaway($_SESSION['photo_return']);
}
@ -472,7 +472,7 @@ function photos_post(App $a)
$uri = Item::newURI($page_owner_uid);
$arr = [];
$arr['guid'] = System::createGUID(32);
$arr['guid'] = System::createUUID();
$arr['uid'] = $page_owner_uid;
$arr['uri'] = $uri;
$arr['parent-uri'] = $uri;
@ -651,7 +651,7 @@ function photos_post(App $a)
$uri = Item::newURI($page_owner_uid);
$arr = [];
$arr['guid'] = System::createGUID(32);
$arr['guid'] = System::createUUID();
$arr['uid'] = $page_owner_uid;
$arr['uri'] = $uri;
$arr['parent-uri'] = $uri;
@ -762,12 +762,14 @@ function photos_post(App $a)
$filesize = $ret['filesize'];
$type = $ret['type'];
$error = UPLOAD_ERR_OK;
} else {
} elseif (!empty($_FILES['userfile'])) {
$src = $_FILES['userfile']['tmp_name'];
$filename = basename($_FILES['userfile']['name']);
$filesize = intval($_FILES['userfile']['size']);
$type = $_FILES['userfile']['type'];
$error = $_FILES['userfile']['error'];
} else {
$error = UPLOAD_ERR_NO_FILE;
}
if ($error !== UPLOAD_ERR_OK) {
@ -887,7 +889,7 @@ function photos_post(App $a)
$arr['coord'] = $lat . ' ' . $lon;
}
$arr['guid'] = System::createGUID(32);
$arr['guid'] = System::createUUID();
$arr['uid'] = $page_owner_uid;
$arr['uri'] = $uri;
$arr['parent-uri'] = $uri;
@ -1141,8 +1143,8 @@ function photos_content(App $a)
DBA::escape($album)
);
if (DBA::isResult($r)) {
$a->set_pager_total(count($r));
$a->set_pager_itemspage(20);
$a->setPagerTotal(count($r));
$a->setPagerItemsPage(20);
}
/// @TODO I have seen this many times, maybe generalize it script-wide and encapsulate it?
@ -1391,7 +1393,7 @@ function photos_content(App $a)
$link_item = Item::selectFirst([], ['id' => $linked_items[0]['id']]);
$condition = ["`parent` = ? AND `parent` != `id`", $link_item['parent']];
$a->set_pager_total(DBA::count('item', $condition));
$a->setPagerTotal(DBA::count('item', $condition));
$params = ['order' => ['id'], 'limit' => [$a->pager['start'], $a->pager['itemspage']]];
$result = Item::selectForUser($link_item['uid'], Item::ITEM_FIELDLIST, $condition, $params);
@ -1633,7 +1635,7 @@ function photos_content(App $a)
'$paginate' => $paginate,
]);
$a->page['htmlhead'] .= "\n" . '<meta name="twitter:card" content="photo" />' . "\n";
$a->page['htmlhead'] .= "\n" . '<meta name="twitter:card" content="summary_large_image" />' . "\n";
$a->page['htmlhead'] .= '<meta name="twitter:title" content="' . $photo["album"] . '" />' . "\n";
$a->page['htmlhead'] .= '<meta name="twitter:image" content="' . $photo["href"] . '" />' . "\n";
$a->page['htmlhead'] .= '<meta name="twitter:image:width" content="' . $photo["width"] . '" />' . "\n";
@ -1653,8 +1655,8 @@ function photos_content(App $a)
);
if (DBA::isResult($r)) {
$a->set_pager_total(count($r));
$a->set_pager_itemspage(20);
$a->setPagerTotal(count($r));
$a->setPagerItemsPage(20);
}
$r = q("SELECT `resource-id`, ANY_VALUE(`id`) AS `id`, ANY_VALUE(`filename`) AS `filename`,

View file

@ -202,11 +202,7 @@ function ping_init(App $a)
$mail_count = count($mails);
if (intval(Config::get('config', 'register_policy')) === REGISTER_APPROVE && is_site_admin()) {
$regs = q(
"SELECT `contact`.`name`, `contact`.`url`, `contact`.`micro`, `register`.`created`
FROM `contact` RIGHT JOIN `register` ON `register`.`uid` = `contact`.`uid`
WHERE `contact`.`self` = 1"
);
$regs = Friendica\Model\Register::getPending();
if (DBA::isResult($regs)) {
$register_count = count($regs);
@ -350,7 +346,7 @@ function ping_init(App $a)
$regularnotifications = (!empty($_GET['uid']) && !empty($_GET['_']));
foreach ($notifs as $notif) {
if ($a->is_friendica_app() || !$regularnotifications) {
if ($a->isFriendicaApp() || !$regularnotifications) {
$notif['message'] = str_replace("{0}", $notif['name'], $notif['message']);
}
@ -510,16 +506,17 @@ function ping_get_notifications($uid)
* @brief Backward-compatible XML formatting for ping.php output
* @deprecated
*
* @param array $data The initial ping data array
* @param int $sysnotify Number of unseen system notifications
* @param array $notifs Complete list of notification
* @param array $sysmsgs List of system notice messages
* @param array $sysmsgs_info List of system info messages
* @param int $groups_unseen Number of unseen group items
* @param int $forums_unseen Number of unseen forum items
* @param array $data The initial ping data array
* @param int $sysnotify_count Number of unseen system notifications
* @param array $notifs Complete list of notification
* @param array $sysmsgs List of system notice messages
* @param array $sysmsgs_info List of system info messages
* @param int $groups_unseen Number of unseen group items
* @param int $forums_unseen Number of unseen forum items
*
* @return array XML-transform ready data array
*/
function ping_format_xml_data($data, $sysnotify, $notifs, $sysmsgs, $sysmsgs_info, $groups_unseen, $forums_unseen)
function ping_format_xml_data($data, $sysnotify_count, $notifs, $sysmsgs, $sysmsgs_info, $groups_unseen, $forums_unseen)
{
$notifications = [];
foreach ($notifs as $key => $notif) {

View file

@ -24,19 +24,20 @@ use Friendica\Model\Item;
require_once 'include/security.php';
require_once 'include/items.php';
function poke_init(App $a) {
function poke_init(App $a)
{
if (!local_user()) {
return;
}
$uid = local_user();
$verb = notags(trim($_GET['verb']));
if (!$verb) {
if (empty($_GET['verb'])) {
return;
}
$verb = notags(trim($_GET['verb']));
$verbs = get_poke_verbs();
if (!array_key_exists($verb, $verbs)) {
@ -96,10 +97,10 @@ function poke_init(App $a) {
$arr = [];
$arr['guid'] = System::createGUID(32);
$arr['guid'] = System::createUUID();
$arr['uid'] = $uid;
$arr['uri'] = $uri;
$arr['parent-uri'] = ($parent_uri ? $parent_uri : $uri);
$arr['parent-uri'] = (!empty($parent_uri) ? $parent_uri : $uri);
$arr['wall'] = 1;
$arr['contact-id'] = $poster['id'];
$arr['owner-name'] = $poster['name'];
@ -121,7 +122,7 @@ function poke_init(App $a) {
$arr['origin'] = 1;
$arr['body'] = '[url=' . $poster['url'] . ']' . $poster['name'] . '[/url]' . ' ' . L10n::t($verbs[$verb][0]) . ' ' . '[url=' . $target['url'] . ']' . $target['name'] . '[/url]';
$arr['object'] = '<object><type>' . ACTIVITY_OBJ_PERSON . '</type><title>' . $target['name'] . '</title><id>' . System::baseUrl() . '/contact/' . $target['id'] . '</id>';
$arr['object'] = '<object><type>' . ACTIVITY_OBJ_PERSON . '</type><title>' . $target['name'] . '</title><id>' . $target['url'] . '</id>';
$arr['object'] .= '<link>' . xmlify('<link rel="alternate" type="text/html" href="' . $target['url'] . '" />' . "\n");
$arr['object'] .= xmlify('<link rel="photo" type="image/jpeg" href="' . $target['photo'] . '" />' . "\n");
@ -137,10 +138,8 @@ function poke_init(App $a) {
return;
}
function poke_content(App $a) {
function poke_content(App $a)
{
if (!local_user()) {
notice(L10n::t('Permission denied.') . EOL);
return;
@ -149,17 +148,17 @@ function poke_content(App $a) {
$name = '';
$id = '';
if (intval($_GET['c'])) {
$r = q("SELECT `id`,`name` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
intval($_GET['c']),
intval(local_user())
);
if (DBA::isResult($r)) {
$name = $item['name'];
$id = $item['id'];
}
if (empty($_GET['c'])) {
return;
}
$contact = DBA::selectFirst('contact', ['id', 'name'], ['id' => $_GET['c'], 'uid' => local_user()]);
if (!DBA::isResult($contact)) {
return;
}
$name = $contact['name'];
$id = $contact['id'];
$base = System::baseUrl();

View file

@ -20,6 +20,7 @@ use Friendica\Model\Profile;
use Friendica\Module\Login;
use Friendica\Protocol\DFRN;
use Friendica\Util\DateTimeFormat;
use Friendica\Protocol\ActivityPub;
function profile_init(App $a)
{
@ -49,6 +50,16 @@ function profile_init(App $a)
DFRN::autoRedir($a, $which);
}
if (ActivityPub::isRequest()) {
$user = DBA::selectFirst('user', ['uid'], ['nickname' => $which]);
if (DBA::isResult($user)) {
$data = ActivityPub\Transmitter::getProfile($user['uid']);
echo json_encode($data);
header('Content-Type: application/activity+json');
exit();
}
}
Profile::load($a, $which, $profile);
$blocked = !local_user() && !remote_user() && Config::get('system', 'block_public');
@ -80,7 +91,7 @@ function profile_init(App $a)
$a->page['htmlhead'] .= '<link rel="alternate" type="application/atom+xml" href="' . System::baseUrl() . '/feed/' . $which . '/" title="' . L10n::t('%s\'s posts', $a->profile['username']) . '"/>' . "\r\n";
$a->page['htmlhead'] .= '<link rel="alternate" type="application/atom+xml" href="' . System::baseUrl() . '/feed/' . $which . '/comments" title="' . L10n::t('%s\'s comments', $a->profile['username']) . '"/>' . "\r\n";
$a->page['htmlhead'] .= '<link rel="alternate" type="application/atom+xml" href="' . System::baseUrl() . '/feed/' . $which . '/activity" title="' . L10n::t('%s\'s timeline', $a->profile['username']) . '"/>' . "\r\n";
$uri = urlencode('acct:' . $a->profile['nickname'] . '@' . $a->get_hostname() . ($a->urlpath ? '/' . $a->urlpath : ''));
$uri = urlencode('acct:' . $a->profile['nickname'] . '@' . $a->getHostName() . ($a->getURLPath() ? '/' . $a->getURLPath() : ''));
$a->page['htmlhead'] .= '<link rel="lrdd" type="application/xrd+xml" href="' . System::baseUrl() . '/xrd/?uri=' . $uri . '" />' . "\r\n";
header('Link: <' . System::baseUrl() . '/xrd/?uri=' . $uri . '>; rel="lrdd"; type="application/xrd+xml"', false);
@ -296,7 +307,7 @@ function profile_content(App $a, $update = 0)
$itemspage_network = $a->force_max_items;
}
$a->set_pager_itemspage($itemspage_network);
$a->setPagerItemsPage($itemspage_network);
$pager_sql = sprintf(" LIMIT %d, %d ", intval($a->pager['start']), intval($a->pager['itemspage']));

View file

@ -317,7 +317,6 @@ function profile_photo_crop_ui_head(App $a, Image $image)
}
$a->page['htmlhead'] .= replace_macros(get_markup_template("crophead.tpl"), []);
$a->page['end'] .= replace_macros(get_markup_template("cropend.tpl"), []);
$imagecrop = [
'hash' => $hash,

View file

@ -20,6 +20,7 @@ use Friendica\Model\Profile;
use Friendica\Network\Probe;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Temporal;
use Friendica\Module\Login;
function profiles_init(App $a) {
@ -509,7 +510,7 @@ function profiles_content(App $a) {
if (! local_user()) {
notice(L10n::t('Permission denied.') . EOL);
return;
return Login::form();
}
$o = '';
@ -527,9 +528,6 @@ function profiles_content(App $a) {
$a->page['htmlhead'] .= replace_macros(get_markup_template('profed_head.tpl'), [
'$baseurl' => System::baseUrl(true),
]);
$a->page['end'] .= replace_macros(get_markup_template('profed_end.tpl'), [
'$baseurl' => System::baseUrl(true),
]);
$opt_tpl = get_markup_template("profile-hide-friends.tpl");
$hide_friends = replace_macros($opt_tpl,[
@ -618,10 +616,10 @@ function profiles_content(App $a) {
'$country_name' => ['country_name', L10n::t('Country:'), $r[0]['country-name']],
'$age' => ((intval($r[0]['dob'])) ? '(' . L10n::t('Age: ') . Temporal::getAgeByTimezone($r[0]['dob'],$a->user['timezone'],$a->user['timezone']) . ')' : ''),
'$gender' => ContactSelector::gender($r[0]['gender']),
'$marital' => ContactSelector::maritalStatus($r[0]['marital']),
'$marital' => ['selector' => ContactSelector::maritalStatus($r[0]['marital']), 'value' => $r[0]['marital']],
'$with' => ['with', L10n::t("Who: \x28if applicable\x29"), strip_tags($r[0]['with']), L10n::t('Examples: cathy123, Cathy Williams, cathy@example.com')],
'$howlong' => ['howlong', L10n::t('Since [date]:'), ($r[0]['howlong'] <= NULL_DATE ? '' : DateTimeFormat::local($r[0]['howlong']))],
'$sexual' => ContactSelector::sexualPreference($r[0]['sexual']),
'$sexual' => ['selector' => ContactSelector::sexualPreference($r[0]['sexual']), 'value' => $r[0]['sexual']],
'$about' => ['about', L10n::t('Tell us about yourself...'), $r[0]['about']],
'$xmpp' => ['xmpp', L10n::t("XMPP \x28Jabber\x29 address:"), $r[0]['xmpp'], L10n::t("The XMPP address will be propagated to your contacts so that they can follow you.")],
'$homepage' => ['homepage', L10n::t('Homepage URL:'), $r[0]['homepage']],
@ -669,7 +667,7 @@ function profiles_content(App $a) {
$profiles = '';
foreach ($r as $rr) {
$profiles .= replace_macros($tpl, [
'$photo' => $a->remove_baseurl($rr['thumb']),
'$photo' => $a->removeBaseURL($rr['thumb']),
'$id' => $rr['id'],
'$alt' => L10n::t('Profile Image'),
'$profile_name' => $rr['profile-name'],

View file

@ -104,8 +104,9 @@ function pubsubhubbub_init(App $a) {
// we don't actually enforce the lease time because GNU
// Social/StatusNet doesn't honour it (yet)
$body = Network::fetchUrl($hub_callback . "?" . $params);
$ret = $a->get_curl_code();
$fetchResult = Network::fetchUrlFull($hub_callback . "?" . $params);
$body = $fetchResult->getBody();
$ret = $fetchResult->getReturnCode();
// give up if the HTTP return code wasn't a success (2xx)
if ($ret < 200 || $ret > 299) {

View file

@ -57,7 +57,7 @@ function redir_init(App $a) {
}
if (remote_user()) {
$host = substr(System::baseUrl() . ($a->urlpath ? '/' . $a->urlpath : ''), strpos(System::baseUrl(), '://') + 3);
$host = substr(System::baseUrl() . ($a->getURLPath() ? '/' . $a->getURLPath() : ''), strpos(System::baseUrl(), '://') + 3);
$remotehost = substr($contact['addr'], strpos($contact['addr'], '@') + 1);
// On a local instance we have to check if the local user has already authenticated

View file

@ -11,10 +11,8 @@ use Friendica\Core\L10n;
use Friendica\Core\PConfig;
use Friendica\Core\System;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\Model\User;
use Friendica\Model;
use Friendica\Module\Tos;
use Friendica\Util\DateTimeFormat;
require_once 'include/enotify.php';
@ -67,7 +65,7 @@ function register_post(App $a)
$arr['language'] = L10n::getBrowserLanguage();
try {
$result = User::create($arr);
$result = Model\User::create($arr);
} catch (Exception $e) {
notice($e->getMessage());
return;
@ -76,7 +74,7 @@ function register_post(App $a)
$user = $result['user'];
if ($netpublish && intval(Config::get('config', 'register_policy')) !== REGISTER_APPROVE) {
$url = System::baseUrl() . '/profile/' . $user['nickname'];
$url = $a->getBaseUrl() . '/profile/' . $user['nickname'];
Worker::add(PRIORITY_LOW, "Directory", $url);
}
@ -86,18 +84,22 @@ function register_post(App $a)
if (intval(Config::get('config', 'register_policy')) === REGISTER_OPEN) {
if ($using_invites && $invite_id) {
q("delete * from register where hash = '%s' limit 1", DBA::escape($invite_id));
Model\Register::deleteByHash($invite_id);
PConfig::set($user['uid'], 'system', 'invites_remaining', $num_invites);
}
// Only send a password mail when the password wasn't manually provided
if (!x($_POST, 'password1') || !x($_POST, 'confirm')) {
$res = User::sendRegisterOpenEmail(
$user['email'], Config::get('config', 'sitename'), System::baseUrl(), $user['username'], $result['password'], $user);
$res = Model\User::sendRegisterOpenEmail(
$user,
Config::get('config', 'sitename'),
$a->getBaseUrl(),
$result['password']
);
if ($res) {
info(L10n::t('Registration successful. Please check your email for further instructions.') . EOL);
goaway(System::baseUrl());
goaway();
} else {
notice(
L10n::t('Failed to send email message. Here your accout details:<br> login: %s<br> password: %s<br><br>You can change your password after login.',
@ -108,27 +110,19 @@ function register_post(App $a)
}
} else {
info(L10n::t('Registration successful.') . EOL);
goaway(System::baseUrl());
goaway();
}
} elseif (intval(Config::get('config', 'register_policy')) === REGISTER_APPROVE) {
if (!strlen(Config::get('config', 'admin_email'))) {
notice(L10n::t('Your registration can not be processed.') . EOL);
goaway(System::baseUrl());
goaway();
}
$hash = random_string();
$r = q("INSERT INTO `register` ( `hash`, `created`, `uid`, `password`, `language`, `note` ) VALUES ( '%s', '%s', %d, '%s', '%s', '%s' ) ",
DBA::escape($hash),
DBA::escape(DateTimeFormat::utcNow()),
intval($user['uid']),
DBA::escape($result['password']),
DBA::escape(Config::get('system', 'language')),
DBA::escape($_POST['permonlybox'])
);
Model\Register::createForApproval($user['uid'], Config::get('system', 'language'), $_POST['permonlybox']);
// invite system
if ($using_invites && $invite_id) {
q("DELETE * FROM `register` WHERE `hash` = '%s' LIMIT 1", DBA::escape($invite_id));
Model\Register::deleteByHash($invite_id);
PConfig::set($user['uid'], 'system', 'invites_remaining', $num_invites);
}
@ -146,9 +140,9 @@ function register_post(App $a)
'source_name' => $user['username'],
'source_mail' => $user['email'],
'source_nick' => $user['nickname'],
'source_link' => System::baseUrl() . "/admin/users/",
'link' => System::baseUrl() . "/admin/users/",
'source_photo' => System::baseUrl() . "/photo/avatar/" . $user['uid'] . ".jpg",
'source_link' => $a->getBaseUrl() . "/admin/users/",
'link' => $a->getBaseUrl() . "/admin/users/",
'source_photo' => $a->getBaseUrl() . "/photo/avatar/" . $user['uid'] . ".jpg",
'to_email' => $admin['email'],
'uid' => $admin['uid'],
'language' => $admin['language'] ? $admin['language'] : 'en',
@ -156,11 +150,15 @@ function register_post(App $a)
]);
}
// send notification to the user, that the registration is pending
User::sendRegisterPendingEmail(
$user['email'], Config::get('config', 'sitename'), $user['username']);
Model\User::sendRegisterPendingEmail(
$user,
Config::get('config', 'sitename'),
$a->getBaseURL(),
$result['password']
);
info(L10n::t('Your registration is pending approval by the site owner.') . EOL);
goaway(System::baseUrl());
goaway();
}
return;
@ -274,7 +272,7 @@ function register_content(App $a)
'$passwords' => $passwords,
'$password1' => ['password1', L10n::t('New Password:'), '', L10n::t('Leave empty for an auto generated password.')],
'$password2' => ['confirm', L10n::t('Confirm:'), '', ''],
'$nickdesc' => L10n::t('Choose a profile nickname. This must begin with a text character. Your profile address on this site will then be \'<strong>nickname@%s</strong>\'.', $a->get_hostname()),
'$nickdesc' => L10n::t('Choose a profile nickname. This must begin with a text character. Your profile address on this site will then be \'<strong>nickname@%s</strong>\'.', $a->getHostName()),
'$nicklabel' => L10n::t('Choose a nickname: '),
'$photo' => $photo,
'$publish' => $profile_publish,
@ -283,7 +281,7 @@ function register_content(App $a)
'$email' => $email,
'$nickname' => $nickname,
'$license' => $license,
'$sitename' => $a->get_hostname(),
'$sitename' => $a->getHostName(),
'$importh' => L10n::t('Import'),
'$importt' => L10n::t('Import your profile to this friendica instance'),
'$showtoslink' => Config::get('system', 'tosdisplay'),

View file

@ -9,6 +9,7 @@ use Friendica\Core\L10n;
use Friendica\Core\System;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\Model\Register;
use Friendica\Model\User;
use Friendica\Module\Login;
@ -18,51 +19,35 @@ function user_allow($hash)
{
$a = get_app();
$register = q("SELECT * FROM `register` WHERE `hash` = '%s' LIMIT 1",
DBA::escape($hash)
);
$register = Register::getByHash($hash);
if (!DBA::isResult($register)) {
return false;
}
$user = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
intval($register[0]['uid'])
);
$user = User::getById($register['uid']);
if (!DBA::isResult($user)) {
killme();
exit();
}
$r = q("DELETE FROM `register` WHERE `hash` = '%s'",
DBA::escape($register[0]['hash'])
);
Register::deleteByHash($hash);
DBA::update('user', ['blocked' => false, 'verified' => true], ['uid' => $register['uid']]);
$r = q("UPDATE `user` SET `blocked` = 0, `verified` = 1 WHERE `uid` = %d",
intval($register[0]['uid'])
);
$profile = DBA::selectFirst('profile', ['net-publish'], ['uid' => $register['uid'], 'is-default' => true]);
$r = q("SELECT * FROM `profile` WHERE `uid` = %d AND `is-default` = 1",
intval($user[0]['uid'])
);
if (DBA::isResult($r) && $r[0]['net-publish']) {
$url = System::baseUrl() . '/profile/' . $user[0]['nickname'];
if ($url && strlen(Config::get('system', 'directory'))) {
Worker::add(PRIORITY_LOW, "Directory", $url);
}
if (DBA::isResult($profile) && $profile['net-publish'] && Config::get('system', 'directory')) {
$url = System::baseUrl() . '/profile/' . $user['nickname'];
Worker::add(PRIORITY_LOW, "Directory", $url);
}
L10n::pushLang($register[0]['language']);
L10n::pushLang($register['language']);
$res = User::sendRegisterOpenEmail(
$user[0]['email'],
$user,
Config::get('config', 'sitename'),
System::baseUrl(),
$user[0]['username'],
$register[0]['password'],
$user[0]);
$a->getBaseUrl(),
defaults($register, 'password', 'Sent in a previous email')
);
L10n::popLang();
@ -77,22 +62,21 @@ function user_allow($hash)
// allowed to have friends on this system
function user_deny($hash)
{
$register = q("SELECT * FROM `register` WHERE `hash` = '%s' LIMIT 1",
DBA::escape($hash)
);
$register = Register::getByHash($hash);
if (!DBA::isResult($register)) {
return false;
}
$user = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
intval($register[0]['uid'])
);
$user = User::getById($register['uid']);
if (!DBA::isResult($user)) {
exit();
}
DBA::delete('user', ['uid' => $register[0]['uid']]);
DBA::delete('register', ['hash' => $register[0]['hash']]);
DBA::delete('user', ['uid' => $register['uid']]);
notice(L10n::t('Registration revoked for %s', $user[0]['username']) . EOL);
Register::deleteByHash($register['hash']);
notice(L10n::t('Registration revoked for %s', $user['username']) . EOL);
return true;
}
@ -100,17 +84,16 @@ function regmod_content(App $a)
{
if (!local_user()) {
info(L10n::t('Please login.') . EOL);
$o = '<br /><br />' . Login::form($a->query_string, intval(Config::get('config', 'register_policy')) === REGISTER_CLOSED ? 0 : 1);
return $o;
return Login::form($a->query_string, intval(Config::get('config', 'register_policy')) === REGISTER_CLOSED ? 0 : 1);
}
if ((!is_site_admin()) || (x($_SESSION, 'submanage') && intval($_SESSION['submanage']))) {
if (!is_site_admin() || !empty($_SESSION['submanage'])) {
notice(L10n::t('Permission denied.') . EOL);
return '';
}
if ($a->argc != 3) {
killme();
exit();
}
$cmd = $a->argv[1];
@ -118,13 +101,11 @@ function regmod_content(App $a)
if ($cmd === 'deny') {
user_deny($hash);
goaway(System::baseUrl() . "/admin/users/");
killme();
goaway('admin/users/');
}
if ($cmd === 'allow') {
user_allow($hash);
goaway(System::baseUrl() . "/admin/users/");
killme();
goaway('admin/users/');
}
}

View file

@ -14,7 +14,7 @@ function repair_ostatus_content(App $a) {
if (! local_user()) {
notice(L10n::t('Permission denied.') . EOL);
goaway($_SESSION['return_url']);
goaway('/ostatus_repair');
// NOTREACHED
}

View file

@ -41,14 +41,14 @@ function salmon_post(App $a, $xml = '') {
$base = null;
// figure out where in the DOM tree our data is hiding
if($dom->provenance->data)
if (!empty($dom->provenance->data))
$base = $dom->provenance;
elseif($dom->env->data)
elseif (!empty($dom->env->data))
$base = $dom->env;
elseif($dom->data)
elseif (!empty($dom->data))
$base = $dom;
if(! $base) {
if (empty($base)) {
logger('unable to locate salmon data in xml ');
System::httpExit(400);
}

View file

@ -22,6 +22,7 @@ use Friendica\Model\User;
use Friendica\Protocol\Email;
use Friendica\Util\Network;
use Friendica\Util\Temporal;
use Friendica\Module\Login;
function get_theme_config_file($theme)
{
@ -546,7 +547,7 @@ function settings_post(App $a)
if ($openid != $a->user['openid'] || (strlen($openid) && (!strlen($openidserver)))) {
if (Network::isUrlValid($openid)) {
logger('updating openidserver');
$open_id_obj = new LightOpenID($a->get_hostname());
$open_id_obj = new LightOpenID($a->getHostName());
$open_id_obj->identity = $openid;
$openidserver = $open_id_obj->discover($open_id_obj->identity);
} else {
@ -658,7 +659,7 @@ function settings_content(App $a)
if (!local_user()) {
//notice(L10n::t('Permission denied.') . EOL);
return;
return Login::form();
}
if (x($_SESSION, 'submanage') && intval($_SESSION['submanage'])) {
@ -982,11 +983,6 @@ function settings_content(App $a)
'$theme_config' => $theme_config,
]);
$tpl = get_markup_template('settings/display_end.tpl');
$a->page['end'] .= replace_macros($tpl, [
'$theme' => ['theme', L10n::t('Display Theme:'), $theme_selected, '', $themes]
]);
return $o;
}
@ -1140,8 +1136,8 @@ function settings_content(App $a)
$tpl_addr = get_markup_template('settings/nick_set.tpl');
$prof_addr = replace_macros($tpl_addr,[
'$desc' => L10n::t("Your Identity Address is <strong>'%s'</strong> or '%s'.", $nickname . '@' . $a->get_hostname() . $a->get_path(), System::baseUrl() . '/profile/' . $nickname),
'$basepath' => $a->get_hostname()
'$desc' => L10n::t("Your Identity Address is <strong>'%s'</strong> or '%s'.", $nickname . '@' . $a->getHostName() . $a->getURLPath(), System::baseUrl() . '/profile/' . $nickname),
'$basepath' => $a->getHostName()
]);
$stpl = get_markup_template('settings/settings.tpl');

View file

@ -108,7 +108,7 @@ EOT;
$arr = [];
$arr['guid'] = System::createGUID(32);
$arr['guid'] = System::createUUID();
$arr['uri'] = $uri;
$arr['uid'] = $owner_uid;
$arr['contact-id'] = $contact['id'];

View file

@ -115,7 +115,7 @@ EOT;
$arr = [];
$arr['guid'] = System::createGUID(32);
$arr['guid'] = System::createUUID();
$arr['uri'] = $uri;
$arr['uid'] = $owner_uid;
$arr['contact-id'] = $contact['id'];

View file

@ -10,132 +10,144 @@ use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Model\Profile;
use Friendica\Model\User;
function unfollow_post(App $a)
function unfollow_post()
{
$return_url = 'contacts';
if (!local_user()) {
notice(L10n::t('Permission denied.') . EOL);
goaway($_SESSION['return_url']);
notice(L10n::t('Permission denied.'));
goaway('/login');
// NOTREACHED
}
if ($_REQUEST['cancel']) {
goaway($_SESSION['return_url']);
}
$uid = local_user();
$url = notags(trim($_REQUEST['url']));
$return_url = $_SESSION['return_url'];
$url = notags(trim(defaults($_REQUEST, 'url', '')));
$condition = ["`uid` = ? AND `rel` = ? AND (`nurl` = ? OR `alias` = ? OR `alias` = ?) AND `network` != ?",
$uid, Contact::FRIEND, normalise_link($url),
normalise_link($url), $url, Protocol::STATUSNET];
$condition = ["`uid` = ? AND (`rel` = ? OR `rel` = ?) AND (`nurl` = ? OR `alias` = ? OR `alias` = ?)",
$uid, Contact::SHARING, Contact::FRIEND, normalise_link($url),
normalise_link($url), $url];
$contact = DBA::selectFirst('contact', [], $condition);
if (!DBA::isResult($contact)) {
notice(L10n::t("Contact wasn't found or can't be unfollowed."));
} else {
if (in_array($contact['network'], [Protocol::OSTATUS, Protocol::DIASPORA, Protocol::DFRN])) {
$r = q("SELECT `contact`.*, `user`.* FROM `contact` INNER JOIN `user` ON `contact`.`uid` = `user`.`uid`
WHERE `user`.`uid` = %d AND `contact`.`self` LIMIT 1",
intval($uid)
);
if (DBA::isResult($r)) {
Contact::terminateFriendship($r[0], $contact);
}
}
DBA::update('contact', ['rel' => Contact::FOLLOWER], ['id' => $contact['id']]);
info(L10n::t('Contact unfollowed').EOL);
goaway(System::baseUrl().'/contacts/'.$contact['id']);
notice(L10n::t("You aren't following this contact."));
goaway($return_url);
// NOTREACHED
}
goaway($return_url);
if (!empty($_REQUEST['cancel'])) {
goaway($return_url . '/' . $contact['id']);
}
if (!in_array($contact['network'], Protocol::NATIVE_SUPPORT)) {
notice(L10n::t('Unfollowing is currently not supported by your network.'));
goaway($return_url . '/' . $contact['id']);
// NOTREACHED
}
$dissolve = ($contact['rel'] == Contact::SHARING);
$owner = User::getOwnerDataById($uid);
if ($owner) {
Contact::terminateFriendship($owner, $contact, $dissolve);
}
// Sharing-only contacts get deleted as there no relationship any more
if ($dissolve) {
Contact::remove($contact['id']);
$return_path = 'contacts';
} else {
DBA::update('contact', ['rel' => Contact::FOLLOWER], ['id' => $contact['id']]);
$return_path = 'contact/' . $contact['id'];
}
info(L10n::t('Contact unfollowed'));
goaway($return_path);
// NOTREACHED
}
function unfollow_content(App $a)
{
if (! local_user()) {
notice(L10n::t('Permission denied.') . EOL);
goaway($_SESSION['return_url']);
$return_url = 'contacts';
if (!local_user()) {
notice(L10n::t('Permission denied.'));
goaway('/login');
// NOTREACHED
}
$uid = local_user();
$url = notags(trim($_REQUEST['url']));
$submit = L10n::t('Submit Request');
$condition = ["`uid` = ? AND `rel` = ? AND (`nurl` = ? OR `alias` = ? OR `alias` = ?) AND `network` != ?",
local_user(), Contact::FRIEND, normalise_link($url),
normalise_link($url), $url, Protocol::STATUSNET];
$condition = ["`uid` = ? AND (`rel` = ? OR `rel` = ?) AND (`nurl` = ? OR `alias` = ? OR `alias` = ?)",
local_user(), Contact::SHARING, Contact::FRIEND, normalise_link($url),
normalise_link($url), $url];
$contact = DBA::selectFirst('contact', ['url', 'network', 'addr', 'name'], $condition);
if (!DBA::isResult($contact)) {
notice(L10n::t("You aren't a friend of this contact.").EOL);
$submit = "";
notice(L10n::t("You aren't following this contact."));
goaway($return_url);
// NOTREACHED
}
if (!in_array($contact['network'], [Protocol::DIASPORA, Protocol::OSTATUS, Protocol::DFRN])) {
notice(L10n::t("Unfollowing is currently not supported by your network.").EOL);
$submit = "";
if (!in_array($contact['network'], Protocol::NATIVE_SUPPORT)) {
notice(L10n::t('Unfollowing is currently not supported by your network.'));
goaway('contact/' . $contact['id']);
// NOTREACHED
}
$request = System::baseUrl()."/unfollow";
$request = System::baseUrl() . '/unfollow';
$tpl = get_markup_template('auto_request.tpl');
$r = q("SELECT `url` FROM `contact` WHERE `uid` = %d AND `self` LIMIT 1", intval($uid));
$self = DBA::selectFirst('contact', ['url'], ['uid' => $uid, 'self' => true]);
if (!$r) {
notice(L10n::t('Permission denied.') . EOL);
goaway($_SESSION['return_url']);
if (!DBA::isResult($self)) {
notice(L10n::t('Permission denied.'));
goaway($return_url);
// NOTREACHED
}
$myaddr = $r[0]["url"];
// Makes the connection request for friendica contacts easier
$_SESSION["fastlane"] = $contact["url"];
$_SESSION['fastlane'] = $contact['url'];
$header = L10n::t("Disconnect/Unfollow");
$header = L10n::t('Disconnect/Unfollow');
$o = replace_macros($tpl, [
'$header' => htmlentities($header),
'$desc' => "",
'$pls_answer' => "",
'$does_know_you' => "",
'$add_note' => "",
'$page_desc' => "",
'$friendica' => "",
'$statusnet' => "",
'$diaspora' => "",
'$diasnote' => "",
'$your_address' => L10n::t('Your Identity Address:'),
'$invite_desc' => "",
'$emailnet' => "",
'$submit' => $submit,
'$cancel' => L10n::t('Cancel'),
'$nickname' => "",
'$name' => $contact["name"],
'$url' => $contact["url"],
'$zrl' => Contact::magicLink($contact["url"]),
'$url_label' => L10n::t("Profile URL"),
'$myaddr' => $myaddr,
'$request' => $request,
'$keywords' => "",
'$keywords_label' => ""
$o = replace_macros($tpl, [
'$header' => htmlentities($header),
'$desc' => '',
'$pls_answer' => '',
'$does_know_you' => '',
'$add_note' => '',
'$page_desc' => '',
'$friendica' => '',
'$statusnet' => '',
'$diaspora' => '',
'$diasnote' => '',
'$your_address' => L10n::t('Your Identity Address:'),
'$invite_desc' => '',
'$emailnet' => '',
'$submit' => L10n::t('Submit Request'),
'$cancel' => L10n::t('Cancel'),
'$nickname' => '',
'$name' => $contact['name'],
'$url' => $contact['url'],
'$zrl' => Contact::magicLink($contact['url']),
'$url_label' => L10n::t('Profile URL'),
'$myaddr' => $self['url'],
'$request' => $request,
'$keywords' => '',
'$keywords_label'=> ''
]);
$a->page['aside'] = "";
Profile::load($a, "", 0, Contact::getDetailsByURL($contact["url"]));
$a->page['aside'] = '';
Profile::load($a, '', 0, Contact::getDetailsByURL($contact['url']));
$o .= replace_macros(get_markup_template('section_title.tpl'), ['$title' => L10n::t('Status Messages and Posts')]);
// Show last public posts
$o .= Contact::getPostsFromUrl($contact["url"]);
$o .= Contact::getPostsFromUrl($contact['url']);
return $o;
}

38
mod/update_contacts.php Normal file
View file

@ -0,0 +1,38 @@
<?php
// See update_profile.php for documentation
use Friendica\App;
use Friendica\Core\L10n;
use Friendica\Core\PConfig;
use Friendica\Module\Contact;
function update_contacts_content(App $a)
{
header("Content-type: text/html");
echo "<!DOCTYPE html><html><body>\r\n";
echo "<section>";
if ($_GET["force"] == 1) {
$text = Contact::content($a, true);
} else {
$text = '';
}
if (PConfig::get(local_user(), "system", "bandwidth_saver")) {
$replace = "<br />".L10n::t("[Embedded content - reload page to view]")."<br />";
$pattern = "/<\s*audio[^>]*>(.*?)<\s*\/\s*audio>/i";
$text = preg_replace($pattern, $replace, $text);
$pattern = "/<\s*video[^>]*>(.*?)<\s*\/\s*video>/i";
$text = preg_replace($pattern, $replace, $text);
$pattern = "/<\s*embed[^>]*>(.*?)<\s*\/\s*embed>/i";
$text = preg_replace($pattern, $replace, $text);
$pattern = "/<\s*iframe[^>]*>(.*?)<\s*\/\s*iframe>/i";
$text = preg_replace($pattern, $replace, $text);
}
echo str_replace("\t", " ", $text);
echo "</section>";
echo "</body></html>\r\n";
killme();
}

View file

@ -105,12 +105,6 @@ function videos_init(App $a)
$a->page['htmlhead'] .= replace_macros($tpl,[
'$baseurl' => System::baseUrl(),
]);
$tpl = get_markup_template("videos_end.tpl");
$a->page['end'] .= replace_macros($tpl,[
'$baseurl' => System::baseUrl(),
]);
}
return;
@ -347,8 +341,8 @@ function videos_content(App $a)
);
if (DBA::isResult($r)) {
$a->set_pager_total(count($r));
$a->set_pager_itemspage(20);
$a->setPagerTotal(count($r));
$a->setPagerItemsPage(20);
}
$r = q("SELECT hash, ANY_VALUE(`id`) AS `id`, ANY_VALUE(`created`) AS `created`,
@ -367,11 +361,12 @@ function videos_content(App $a)
foreach ($r as $rr) {
$alt_e = $rr['filename'];
/// @todo The album isn't part of the above query. This seems to be some unfinished code that needs to be reworked completely.
$rr['album'] = '';
$name_e = $rr['album'];
$videos[] = [
'id' => $rr['id'],
'link' => System::baseUrl() . '/videos/' . $a->data['user']['nickname'] . '/video/' . $rr['resource-id'],
'link' => System::baseUrl() . '/videos/' . $a->data['user']['nickname'] . '/video/' . $rr['hash'],
'title' => L10n::t('View Video'),
'src' => System::baseUrl() . '/attach/' . $rr['id'] . '?attachment=0',
'alt' => $alt_e,

View file

@ -71,7 +71,7 @@ function viewcontacts_content(App $a)
DBA::escape(Protocol::OSTATUS)
);
if (DBA::isResult($r)) {
$a->set_pager_total($r[0]['total']);
$a->setPagerTotal($r[0]['total']);
}
$r = q("SELECT * FROM `contact`

View file

@ -26,7 +26,7 @@ function viewsrc_content(App $a)
$item = Item::selectFirst(['body'], ['uid' => local_user(), 'id' => $item_id]);
if (DBA::isResult($item)) {
if (is_ajax()) {
if ($a->isAjax()) {
echo str_replace("\n", '<br />', $item['body']);
killme();
} else {

View file

@ -120,13 +120,6 @@ function wallmessage_content(App $a) {
'$linkurl' => L10n::t('Please enter a link URL:')
]);
$tpl = get_markup_template('wallmsg-end.tpl');
$a->page['end'] .= replace_macros($tpl, [
'$baseurl' => System::baseUrl(true),
'$nickname' => $user['nickname'],
'$linkurl' => L10n::t('Please enter a link URL:')
]);
$tpl = get_markup_template('wallmessage.tpl');
$o = replace_macros($tpl, [
'$header' => L10n::t('Send Private Message'),

View file

@ -23,7 +23,7 @@ function webfinger_content(App $a)
$o = '<h3>Webfinger Diagnostic</h3>';
$o .= '<form action="webfinger" method="get">';
$o .= 'Lookup address: <input type="text" style="width: 250px;" name="addr" value="' . $_GET['addr'] .'" />';
$o .= 'Lookup address: <input type="text" style="width: 250px;" name="addr" value="' . defaults($_GET, 'addr', '') .'" />';
$o .= '<input type="submit" name="submit" value="Submit" /></form>';
$o .= '<br /><br />';

View file

@ -13,7 +13,7 @@ function xrd_init(App $a)
{
if ($a->argv[0] == 'xrd') {
if (empty($_GET['uri'])) {
killme();
System::httpExit(404);
}
$uri = urldecode(notags(trim($_GET['uri'])));
@ -24,7 +24,7 @@ function xrd_init(App $a)
}
} else {
if (empty($_GET['resource'])) {
killme();
System::httpExit(404);
}
$uri = urldecode(notags(trim($_GET['resource'])));
@ -48,16 +48,16 @@ function xrd_init(App $a)
$user = DBA::selectFirst('user', [], ['nickname' => $name]);
if (!DBA::isResult($user)) {
killme();
System::httpExit(404);
}
$profile_url = System::baseUrl().'/profile/'.$user['nickname'];
$alias = str_replace('/profile/', '/~', $profile_url);
$addr = 'acct:'.$user['nickname'].'@'.$a->get_hostname();
if ($a->get_path()) {
$addr .= '/'.$a->get_path();
$addr = 'acct:'.$user['nickname'].'@'.$a->getHostName();
if ($a->getURLPath()) {
$addr .= '/'.$a->getURLPath();
}
if ($mode == 'xml') {
@ -80,6 +80,7 @@ function xrd_json($a, $uri, $alias, $profile_url, $r)
['rel' => NAMESPACE_DFRN, 'href' => $profile_url],
['rel' => NAMESPACE_FEED, 'type' => 'application/atom+xml', 'href' => System::baseUrl().'/dfrn_poll/'.$r['nickname']],
['rel' => 'http://webfinger.net/rel/profile-page', 'type' => 'text/html', 'href' => $profile_url],
['rel' => 'self', 'type' => 'application/activity+json', 'href' => $profile_url],
['rel' => 'http://microformats.org/profile/hcard', 'type' => 'text/html', 'href' => System::baseUrl().'/hcard/'.$r['nickname']],
['rel' => NAMESPACE_POCO, 'href' => System::baseUrl().'/poco/'.$r['nickname']],
['rel' => 'http://webfinger.net/rel/avatar', 'type' => 'image/jpeg', 'href' => System::baseUrl().'/photo/profile/'.$r['uid'].'.jpg'],
@ -92,6 +93,7 @@ function xrd_json($a, $uri, $alias, $profile_url, $r)
['rel' => 'http://purl.org/openwebauth/v1', 'type' => 'application/x-dfrn+json', 'href' => System::baseUrl().'/owa']
]
];
echo json_encode($json);
killme();
}

View file

@ -11,6 +11,7 @@ use Friendica\Core\L10n;
use Friendica\Core\PConfig;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Network\HTTPException\InternalServerErrorException;
require_once 'boot.php';
require_once 'include/dba.php';
@ -31,21 +32,6 @@ require_once 'include/text.php';
*/
class App
{
const MODE_LOCALCONFIGPRESENT = 1;
const MODE_DBAVAILABLE = 2;
const MODE_DBCONFIGAVAILABLE = 4;
const MODE_MAINTENANCEDISABLED = 8;
/**
* @deprecated since version 2008.08 Use App->isInstallMode() instead to check for install mode.
*/
const MODE_INSTALL = 0;
/**
* @deprecated since version 2008.08 Use the precise mode constant to check for a specific capability instead.
*/
const MODE_NORMAL = App::MODE_LOCALCONFIGPRESENT | App::MODE_DBAVAILABLE | App::MODE_DBCONFIGAVAILABLE | App::MODE_MAINTENANCEDISABLED;
public $module_loaded = false;
public $module_class = null;
public $query_string = '';
@ -67,10 +53,7 @@ class App
public $argv;
public $argc;
public $module;
public $mode = App::MODE_INSTALL;
public $strings;
public $basepath;
public $urlpath;
public $hooks = [];
public $timezone;
public $interactive = true;
@ -80,11 +63,9 @@ class App
public $identities;
public $is_mobile = false;
public $is_tablet = false;
public $is_friendica_app;
public $performance = [];
public $callstack = [];
public $theme_info = [];
public $backend = true;
public $nav_sel;
public $category;
// Allow themes to control internal parameters
@ -96,6 +77,76 @@ class App
public $force_max_items = 0;
public $theme_events_in_profile = true;
public $stylesheets = [];
public $footerScripts = [];
/**
* @var App\Mode The Mode of the Application
*/
private $mode;
/**
* @var string The App base path
*/
private $basePath;
/**
* @var string The App URL path
*/
private $urlPath;
/**
* @var bool true, if the call is from the Friendica APP, otherwise false
*/
private $isFriendicaApp;
/**
* @var bool true, if the call is from an backend node (f.e. worker)
*/
private $isBackend;
/**
* @var string The name of the current theme
*/
private $currentTheme;
/**
* @var bool check if request was an AJAX (xmlhttprequest) request
*/
private $isAjax;
/**
* Register a stylesheet file path to be included in the <head> tag of every page.
* Inclusion is done in App->initHead().
* The path can be absolute or relative to the Friendica installation base folder.
*
* @see App->initHead()
*
* @param string $path
*/
public function registerStylesheet($path)
{
$url = str_replace($this->getBasePath() . DIRECTORY_SEPARATOR, '', $path);
$this->stylesheets[] = trim($url, '/');
}
/**
* Register a javascript file path to be included in the <footer> tag of every page.
* Inclusion is done in App->initFooter().
* The path can be absolute or relative to the Friendica installation base folder.
*
* @see App->initFooter()
*
* @param string $path
*/
public function registerFooterScript($path)
{
$url = str_replace($this->getBasePath() . DIRECTORY_SEPARATOR, '', $path);
$this->footerScripts[] = trim($url, '/');
}
/**
* @brief An array for all theme-controllable parameters
*
@ -132,26 +183,26 @@ class App
];
private $scheme;
private $hostname;
private $curl_code;
private $curl_content_type;
private $curl_headers;
/**
* @brief App constructor.
*
* @param string $basepath Path to the app base folder
* @param string $basePath Path to the app base folder
* @param bool $backend true, if the call is from backend, otherwise set to true (Default true)
*
* @throws Exception if the Basepath is not usable
*/
public function __construct($basepath)
public function __construct($basePath, $backend = true)
{
if (!static::directory_usable($basepath, false)) {
throw new Exception('Basepath ' . $basepath . ' isn\'t usable.');
if (!static::isDirectoryUsable($basePath, false)) {
throw new Exception('Basepath ' . $basePath . ' isn\'t usable.');
}
BaseObject::setApp($this);
$this->basepath = rtrim($basepath, DIRECTORY_SEPARATOR);
$this->basePath = rtrim($basePath, DIRECTORY_SEPARATOR);
$this->checkBackend($backend);
$this->checkFriendicaApp();
$this->performance['start'] = microtime(true);
$this->performance['database'] = 0;
@ -174,6 +225,8 @@ class App
$this->callstack['rendering'] = [];
$this->callstack['parser'] = [];
$this->mode = new App\Mode($basePath);
$this->reload();
set_time_limit(0);
@ -203,9 +256,9 @@ class App
set_include_path(
get_include_path() . PATH_SEPARATOR
. $this->basepath . DIRECTORY_SEPARATOR . 'include' . PATH_SEPARATOR
. $this->basepath . DIRECTORY_SEPARATOR . 'library' . PATH_SEPARATOR
. $this->basepath);
. $this->getBasePath() . DIRECTORY_SEPARATOR . 'include' . PATH_SEPARATOR
. $this->getBasePath(). DIRECTORY_SEPARATOR . 'library' . PATH_SEPARATOR
. $this->getBasePath());
if ((x($_SERVER, 'QUERY_STRING')) && substr($_SERVER['QUERY_STRING'], 0, 9) === 'pagename=') {
$this->query_string = substr($_SERVER['QUERY_STRING'], 9);
@ -274,11 +327,26 @@ class App
$this->is_mobile = $mobile_detect->isMobile();
$this->is_tablet = $mobile_detect->isTablet();
// Friendica-Client
$this->is_friendica_app = isset($_SERVER['HTTP_USER_AGENT']) && $_SERVER['HTTP_USER_AGENT'] == 'Apache-HttpClient/UNAVAILABLE (java 1.4)';
$this->isAjax = strtolower(defaults($_SERVER, 'HTTP_X_REQUESTED_WITH', '')) == 'xmlhttprequest';
// Register template engines
$this->register_template_engine('Friendica\Render\FriendicaSmartyEngine');
$this->registerTemplateEngine('Friendica\Render\FriendicaSmartyEngine');
}
/**
* Returns the Mode of the Application
*
* @return App\Mode The Application Mode
*
* @throws InternalServerErrorException when the mode isn't created
*/
public function getMode()
{
if (empty($this->mode)) {
throw new InternalServerErrorException('Mode of the Application is not defined');
}
return $this->mode;
}
/**
@ -291,13 +359,13 @@ class App
$this->loadDatabase();
$this->determineMode();
$this->getMode()->determine($this->getBasePath());
$this->determineUrlPath();
$this->determineURLPath();
Config::load();
if ($this->mode & self::MODE_DBAVAILABLE) {
if ($this->getMode()->has(App\Mode::DBAVAILABLE)) {
Core\Addon::loadHooks();
$this->loadAddonConfig();
@ -309,7 +377,6 @@ class App
'aside' => '',
'bottom' => '',
'content' => '',
'end' => '',
'footer' => '',
'htmlhead' => '',
'nav' => '',
@ -330,20 +397,20 @@ class App
*/
private function loadConfigFiles()
{
$this->loadConfigFile($this->basepath . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'config.ini.php');
$this->loadConfigFile($this->basepath . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'settings.ini.php');
$this->loadConfigFile($this->getBasePath() . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'config.ini.php');
$this->loadConfigFile($this->getBasePath() . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'settings.ini.php');
// Legacy .htconfig.php support
if (file_exists($this->basepath . DIRECTORY_SEPARATOR . '.htpreconfig.php')) {
if (file_exists($this->getBasePath() . DIRECTORY_SEPARATOR . '.htpreconfig.php')) {
$a = $this;
include $this->basepath . DIRECTORY_SEPARATOR . '.htpreconfig.php';
include $this->getBasePath() . DIRECTORY_SEPARATOR . '.htpreconfig.php';
}
// Legacy .htconfig.php support
if (file_exists($this->basepath . DIRECTORY_SEPARATOR . '.htconfig.php')) {
if (file_exists($this->getBasePath() . DIRECTORY_SEPARATOR . '.htconfig.php')) {
$a = $this;
include $this->basepath . DIRECTORY_SEPARATOR . '.htconfig.php';
include $this->getBasePath() . DIRECTORY_SEPARATOR . '.htconfig.php';
$this->setConfigValue('database', 'hostname', $db_host);
$this->setConfigValue('database', 'username', $db_user);
@ -371,14 +438,14 @@ class App
}
}
if (file_exists($this->basepath . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'local.ini.php')) {
$this->loadConfigFile($this->basepath . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'local.ini.php');
if (file_exists($this->getBasePath() . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'local.ini.php')) {
$this->loadConfigFile($this->getBasePath() . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'local.ini.php', true);
}
}
/**
* Tries to load the specified configuration file into the App->config array.
* Overwrites previously set values.
* Doesn't overwrite previously set values by default to prevent default config files to supersede DB Config.
*
* The config format is INI and the template for configuration files is the following:
*
@ -390,10 +457,11 @@ class App
* INI;
* // Keep this line
*
* @param type $filepath
* @param string $filepath
* @param bool $overwrite Force value overwrite if the config key already exists
* @throws Exception
*/
public function loadConfigFile($filepath)
public function loadConfigFile($filepath, $overwrite = false)
{
if (!file_exists($filepath)) {
throw new Exception('Error parsing non-existent config file ' . $filepath);
@ -409,7 +477,11 @@ class App
foreach ($config as $category => $values) {
foreach ($values as $key => $value) {
$this->setConfigValue($category, $key, $value);
if ($overwrite) {
$this->setConfigValue($category, $key, $value);
} else {
$this->setDefaultConfigValue($category, $key, $value);
}
}
}
}
@ -426,8 +498,8 @@ class App
Core\Addon::callHooks('load_config');
// Load the local addon config file to overwritten default addon config values
if (file_exists($this->basepath . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'addon.ini.php')) {
$this->loadConfigFile($this->basepath . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'addon.ini.php');
if (file_exists($this->getBasePath() . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'addon.ini.php')) {
$this->loadConfigFile($this->getBasePath() . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'addon.ini.php', true);
}
}
@ -455,69 +527,39 @@ class App
/**
* Figure out if we are running at the top of a domain or in a sub-directory and adjust accordingly
*/
private function determineUrlPath()
private function determineURLPath()
{
$this->urlpath = $this->getConfigValue('system', 'urlpath');
/* Relative script path to the web server root
* Not all of those $_SERVER properties can be present, so we do by inverse priority order
*/
$relative_script_path = '';
$relative_script_path = defaults($_SERVER, 'REDIRECT_URL' , $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, 'SCRIPT_URL' , $relative_script_path);
/* SCRIPT_URL gives /path/to/friendica/module/parameter
$this->urlPath = $this->getConfigValue('system', 'urlpath');
/* $relative_script_path gives /relative/path/to/friendica/module/parameter
* QUERY_STRING gives pagename=module/parameter
*
* To get /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($_SERVER['SCRIPT_URL'])) {
if (!empty($relative_script_path)) {
// Module
if (!empty($_SERVER['QUERY_STRING'])) {
$path = trim(dirname($_SERVER['SCRIPT_URL'], substr_count(trim($_SERVER['QUERY_STRING'], '/'), '/') + 1), '/');
$path = trim(dirname($relative_script_path, substr_count(trim($_SERVER['QUERY_STRING'], '/'), '/') + 1), '/');
} else {
// Root page
$path = trim($_SERVER['SCRIPT_URL'], '/');
$path = trim($relative_script_path, '/');
}
if ($path && $path != $this->urlpath) {
$this->urlpath = $path;
if ($path && $path != $this->urlPath) {
$this->urlPath = $path;
}
}
}
/**
* Sets the App mode
*
* - App::MODE_INSTALL : Either the database connection can't be established or the config table doesn't exist
* - App::MODE_MAINTENANCE: The maintenance mode has been set
* - App::MODE_NORMAL : Normal run with all features enabled
*
* @return type
*/
private function determineMode()
{
$this->mode = 0;
if (!file_exists($this->basepath . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'local.ini.php')
&& !file_exists($this->basepath . DIRECTORY_SEPARATOR . '.htconfig.php')) {
return;
}
$this->mode |= App::MODE_LOCALCONFIGPRESENT;
if (!DBA::connected()) {
return;
}
$this->mode |= App::MODE_DBAVAILABLE;
if (DBA::fetchFirst("SHOW TABLES LIKE 'config'") === false) {
return;
}
$this->mode |= App::MODE_DBCONFIGAVAILABLE;
if (Config::get('system', 'maintenance')) {
return;
}
$this->mode |= App::MODE_MAINTENANCEDISABLED;
}
public function loadDatabase()
{
if (DBA::connected()) {
@ -554,17 +596,7 @@ class App
DBA::connect($db_host, $db_user, $db_pass, $db_data, $charset);
unset($db_host, $db_user, $db_pass, $db_data, $charset);
$this->save_timestamp($stamp1, 'network');
}
/**
* Install mode is when the local config file is missing or the DB schema hasn't been installed yet.
*
* @return bool
*/
public function isInstallMode()
{
return !($this->mode & App::MODE_LOCALCONFIGPRESENT) || !($this->mode & App::MODE_DBCONFIGAVAILABLE);
$this->saveTimestamp($stamp1, 'network');
}
/**
@ -575,9 +607,9 @@ class App
*
* @return string
*/
public function get_basepath()
public function getBasePath()
{
$basepath = $this->basepath;
$basepath = $this->basePath;
if (!$basepath) {
$basepath = Config::get('system', 'basepath');
@ -591,7 +623,7 @@ class App
$basepath = $_SERVER['PWD'];
}
return self::realpath($basepath);
return self::getRealPath($basepath);
}
/**
@ -604,7 +636,7 @@ class App
* @param string $path The path that is about to be normalized
* @return string normalized path - when possible
*/
public static function realpath($path)
public static function getRealPath($path)
{
$normalized = realpath($path);
@ -615,7 +647,7 @@ class App
}
}
public function get_scheme()
public function getScheme()
{
return $this->scheme;
}
@ -634,7 +666,7 @@ class App
* @param bool $ssl Whether to append http or https under SSL_POLICY_SELFSIGN
* @return string Friendica server base URL
*/
public function get_baseurl($ssl = false)
public function getBaseURL($ssl = false)
{
$scheme = $this->scheme;
@ -657,7 +689,7 @@ class App
$this->hostname = Config::get('config', 'hostname');
}
return $scheme . '://' . $this->hostname . (!empty($this->urlpath) ? '/' . $this->urlpath : '' );
return $scheme . '://' . $this->hostname . (!empty($this->getURLPath()) ? '/' . $this->getURLPath() : '' );
}
/**
@ -667,7 +699,7 @@ class App
*
* @param string $url
*/
public function set_baseurl($url)
public function setBaseURL($url)
{
$parsed = @parse_url($url);
$hostname = '';
@ -685,11 +717,11 @@ class App
$hostname .= ':' . $parsed['port'];
}
if (x($parsed, 'path')) {
$this->urlpath = trim($parsed['path'], '\\/');
$this->urlPath = trim($parsed['path'], '\\/');
}
if (file_exists($this->basepath . DIRECTORY_SEPARATOR . '.htpreconfig.php')) {
include $this->basepath . DIRECTORY_SEPARATOR . '.htpreconfig.php';
if (file_exists($this->getBasePath() . DIRECTORY_SEPARATOR . '.htpreconfig.php')) {
include $this->getBasePath() . DIRECTORY_SEPARATOR . '.htpreconfig.php';
}
if (Config::get('config', 'hostname') != '') {
@ -702,7 +734,7 @@ class App
}
}
public function get_hostname()
public function getHostName()
{
if (Config::get('config', 'hostname') != '') {
$this->hostname = Config::get('config', 'hostname');
@ -711,29 +743,39 @@ class App
return $this->hostname;
}
public function get_path()
public function getURLPath()
{
return $this->urlpath;
return $this->urlPath;
}
public function set_pager_total($n)
public function setPagerTotal($n)
{
$this->pager['total'] = intval($n);
}
public function set_pager_itemspage($n)
public function setPagerItemsPage($n)
{
$this->pager['itemspage'] = ((intval($n) > 0) ? intval($n) : 0);
$this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];
}
public function set_pager_page($n)
public function setPagerPage($n)
{
$this->pager['page'] = $n;
$this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];
}
public function init_pagehead()
/**
* Initializes App->page['htmlhead'].
*
* Includes:
* - Page title
* - Favicons
* - Registered stylesheets (through App->registerStylesheet())
* - Infinite scroll data
* - head.tpl template
*/
public function initHead()
{
$interval = ((local_user()) ? PConfig::get(local_user(), 'system', 'update_interval') : 40000);
@ -754,23 +796,13 @@ class App
$this->page['title'] = $this->config['sitename'];
}
/* put the head template at the beginning of page['htmlhead']
* since the code added by the modules frequently depends on it
* being first
*/
if (!isset($this->page['htmlhead'])) {
$this->page['htmlhead'] = '';
if (!empty($this->theme['stylesheet'])) {
$stylesheet = $this->theme['stylesheet'];
} else {
$stylesheet = $this->getCurrentThemeStylesheetPath();
}
// If we're using Smarty, then doing replace_macros() will replace
// any unrecognized variables with a blank string. Since we delay
// replacing $stylesheet until later, we need to replace it now
// with another variable name
if ($this->theme['template_engine'] === 'smarty3') {
$stylesheet = $this->get_template_ldelim('smarty3') . '$stylesheet' . $this->get_template_rdelim('smarty3');
} else {
$stylesheet = '$stylesheet';
}
$this->registerStylesheet($stylesheet);
$shortcut_icon = Config::get('system', 'shortcut_icon');
if ($shortcut_icon == '') {
@ -783,11 +815,17 @@ class App
}
// get data wich is needed for infinite scroll on the network page
$invinite_scroll = infinite_scroll_data($this->module);
$infinite_scroll = infinite_scroll_data($this->module);
Core\Addon::callHooks('head', $this->page['htmlhead']);
$tpl = get_markup_template('head.tpl');
/* put the head template at the beginning of page['htmlhead']
* since the code added by the modules frequently depends on it
* being first
*/
$this->page['htmlhead'] = replace_macros($tpl, [
'$baseurl' => $this->get_baseurl(),
'$baseurl' => $this->getBaseURL(),
'$local_user' => local_user(),
'$generator' => 'Friendica' . ' ' . FRIENDICA_VERSION,
'$delitem' => L10n::t('Delete this item?'),
@ -796,70 +834,75 @@ class App
'$update_interval' => $interval,
'$shortcut_icon' => $shortcut_icon,
'$touch_icon' => $touch_icon,
'$stylesheet' => $stylesheet,
'$infinite_scroll' => $invinite_scroll,
'$infinite_scroll' => $infinite_scroll,
'$block_public' => intval(Config::get('system', 'block_public')),
'$stylesheets' => $this->stylesheets,
]) . $this->page['htmlhead'];
}
public function init_page_end()
/**
* Initializes App->page['footer'].
*
* Includes:
* - Javascript homebase
* - Mobile toggle link
* - Registered footer scripts (through App->registerFooterScript())
* - footer.tpl template
*/
public function initFooter()
{
if (!isset($this->page['end'])) {
$this->page['end'] = '';
// If you're just visiting, let javascript take you home
if (!empty($_SESSION['visitor_home'])) {
$homebase = $_SESSION['visitor_home'];
} elseif (local_user()) {
$homebase = 'profile/' . $this->user['nickname'];
}
$tpl = get_markup_template('end.tpl');
$this->page['end'] = replace_macros($tpl, [
'$baseurl' => $this->get_baseurl()
]) . $this->page['end'];
}
public function set_curl_code($code)
{
$this->curl_code = $code;
}
if (isset($homebase)) {
$this->page['footer'] .= '<script>var homebase="' . $homebase . '";</script>' . "\n";
}
public function get_curl_code()
{
return $this->curl_code;
}
/*
* Add a "toggle mobile" link if we're using a mobile device
*/
if ($this->is_mobile || $this->is_tablet) {
if (isset($_SESSION['show-mobile']) && !$_SESSION['show-mobile']) {
$link = 'toggle_mobile?address=' . curPageURL();
} else {
$link = 'toggle_mobile?off=1&address=' . curPageURL();
}
$this->page['footer'] .= replace_macros(get_markup_template("toggle_mobile_footer.tpl"), [
'$toggle_link' => $link,
'$toggle_text' => Core\L10n::t('toggle mobile')
]);
}
public function set_curl_content_type($content_type)
{
$this->curl_content_type = $content_type;
}
Core\Addon::callHooks('footer', $this->page['footer']);
public function get_curl_content_type()
{
return $this->curl_content_type;
}
public function set_curl_headers($headers)
{
$this->curl_headers = $headers;
}
public function get_curl_headers()
{
return $this->curl_headers;
$tpl = get_markup_template('footer.tpl');
$this->page['footer'] = replace_macros($tpl, [
'$baseurl' => $this->getBaseURL(),
'$footerScripts' => $this->footerScripts,
]) . $this->page['footer'];
}
/**
* @brief Removes the base url from an url. This avoids some mixed content problems.
*
* @param string $orig_url
* @param string $origURL
*
* @return string The cleaned url
*/
public function remove_baseurl($orig_url)
public function removeBaseURL($origURL)
{
// Remove the hostname from the url if it is an internal link
$nurl = normalise_link($orig_url);
$base = normalise_link($this->get_baseurl());
$nurl = normalise_link($origURL);
$base = normalise_link($this->getBaseURL());
$url = str_replace($base . '/', '', $nurl);
// if it is an external link return the orignal value
if ($url == normalise_link($orig_url)) {
return $orig_url;
if ($url == normalise_link($origURL)) {
return $origURL;
} else {
return $url;
}
@ -870,7 +913,7 @@ class App
*
* @param string $class
*/
private function register_template_engine($class)
private function registerTemplateEngine($class)
{
$v = get_class_vars($class);
if (x($v, 'name')) {
@ -890,7 +933,7 @@ class App
*
* @return object Template Engine instance
*/
public function template_engine()
public function getTemplateEngine()
{
$template_engine = 'smarty3';
if (x($this->theme, 'template_engine')) {
@ -915,35 +958,69 @@ class App
/**
* @brief Returns the active template engine.
*
* @return string
* @return string the active template engine
*/
public function get_template_engine()
public function getActiveTemplateEngine()
{
return $this->theme['template_engine'];
}
public function set_template_engine($engine = 'smarty3')
/**
* sets the active template engine
*
* @param string $engine the template engine (default is Smarty3)
*/
public function setActiveTemplateEngine($engine = 'smarty3')
{
$this->theme['template_engine'] = $engine;
}
public function get_template_ldelim($engine = 'smarty3')
/**
* Gets the right delimiter for a template engine
*
* Currently:
* Internal = ''
* Smarty3 = '{{'
*
* @param string $engine The template engine (default is Smarty3)
*
* @return string the right delimiter
*/
public function getTemplateLeftDelimiter($engine = 'smarty3')
{
return $this->ldelim[$engine];
}
public function get_template_rdelim($engine = 'smarty3')
/**
* Gets the left delimiter for a template engine
*
* Currently:
* Internal = ''
* Smarty3 = '}}'
*
* @param string $engine The template engine (default is Smarty3)
*
* @return string the left delimiter
*/
public function getTemplateRightDelimiter($engine = 'smarty3')
{
return $this->rdelim[$engine];
}
public function save_timestamp($stamp, $value)
/**
* 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)
{
if (!isset($this->config['system']['profiler']) || !$this->config['system']['profiler']) {
return;
}
$duration = (float) (microtime(true) - $stamp);
$duration = (float) (microtime(true) - $timestamp);
if (!isset($this->performance[$value])) {
// Prevent ugly E_NOTICE
@ -963,19 +1040,41 @@ class App
$this->callstack[$value][$callstack] += (float) $duration;
}
public function get_useragent()
/**
* Returns the current UserAgent as a String
*
* @return string the UserAgent as a String
*/
public function getUserAgent()
{
return
FRIENDICA_PLATFORM . " '" .
FRIENDICA_CODENAME . "' " .
FRIENDICA_VERSION . '-' .
DB_UPDATE_VERSION . '; ' .
$this->get_baseurl();
$this->getBaseURL();
}
public function is_friendica_app()
/**
* Checks, if the call is from the Friendica App
*
* Reason:
* The friendica client has problems with the GUID in the notify. this is some workaround
*/
private function checkFriendicaApp()
{
return $this->is_friendica_app;
// Friendica-Client
$this->isFriendicaApp = isset($_SERVER['HTTP_USER_AGENT']) && $_SERVER['HTTP_USER_AGENT'] == 'Apache-HttpClient/UNAVAILABLE (java 1.4)';
}
/**
* Is the call via the Friendica app? (not a "normale" call)
*
* @return bool true if it's from the Friendica app
*/
public function isFriendicaApp()
{
return $this->isFriendicaApp;
}
/**
@ -984,10 +1083,10 @@ class App
* This isn't a perfect solution. But we need this check very early.
* So we cannot wait until the modules are loaded.
*
* @return bool Is it a known backend?
* @param string $backend true, if the backend flag was set during App initialization
*
*/
public function is_backend()
{
private function checkBackend($backend) {
static $backends = [
'_well_known',
'api',
@ -1011,7 +1110,17 @@ class App
];
// Check if current module is in backend or backend flag is set
return (in_array($this->module, $backends) || $this->backend);
$this->isBackend = (in_array($this->module, $backends) || $backend || $this->isBackend);
}
/**
* Returns true, if the call is from a backend node (f.e. from a worker)
*
* @return bool Is it a known backend?
*/
public function isBackend()
{
return $this->isBackend;
}
/**
@ -1059,7 +1168,7 @@ class App
*
* @return bool Is the memory limit reached?
*/
public function min_memory_reached()
public function isMinMemoryReached()
{
$min_memory = Config::get('system', 'min_memory', 0);
if ($min_memory == 0) {
@ -1074,7 +1183,11 @@ class App
$meminfo = [];
foreach ($memdata as $line) {
list($key, $val) = explode(':', $line);
$data = explode(':', $line);
if (count($data) != 2) {
continue;
}
list($key, $val) = $data;
$meminfo[$key] = (int) trim(str_replace('kB', '', $val));
$meminfo[$key] = (int) ($meminfo[$key] / 1024);
}
@ -1101,7 +1214,7 @@ class App
*/
public function isMaxLoadReached()
{
if ($this->is_backend()) {
if ($this->isBackend()) {
$process = 'backend';
$maxsysload = intval(Config::get('system', 'maxloadavg'));
if ($maxsysload < 1) {
@ -1115,7 +1228,7 @@ class App
}
}
$load = current_load();
$load = System::currentLoad();
if ($load) {
if (intval($load) > $maxsysload) {
logger('system: load ' . $load . ' for ' . $process . ' tasks (' . $maxsysload . ') too high.');
@ -1150,14 +1263,14 @@ class App
}
}
if ($this->min_memory_reached()) {
if ($this->isMinMemoryReached()) {
return;
}
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
$resource = proc_open('cmd /c start /b ' . $cmdline, [], $foo, $this->get_basepath());
$resource = proc_open('cmd /c start /b ' . $cmdline, [], $foo, $this->getBasePath());
} else {
$resource = proc_open($cmdline . ' &', [], $foo, $this->get_basepath());
$resource = proc_open($cmdline . ' &', [], $foo, $this->getBasePath());
}
if (!is_resource($resource)) {
logger('We got no resource for command ' . $cmdline, LOGGER_DEBUG);
@ -1173,7 +1286,7 @@ class App
*
* @return string system username
*/
private static function systemuser()
private static function getSystemUser()
{
if (!function_exists('posix_getpwuid') || !function_exists('posix_geteuid')) {
return '';
@ -1188,7 +1301,7 @@ class App
*
* @return boolean the directory is usable
*/
public static function directory_usable($directory, $check_writable = true)
public static function isDirectoryUsable($directory, $check_writable = true)
{
if ($directory == '') {
logger('Directory is empty. This shouldn\'t happen.', LOGGER_DEBUG);
@ -1196,22 +1309,22 @@ class App
}
if (!file_exists($directory)) {
logger('Path "' . $directory . '" does not exist for user ' . self::systemuser(), LOGGER_DEBUG);
logger('Path "' . $directory . '" does not exist for user ' . self::getSystemUser(), LOGGER_DEBUG);
return false;
}
if (is_file($directory)) {
logger('Path "' . $directory . '" is a file for user ' . self::systemuser(), LOGGER_DEBUG);
logger('Path "' . $directory . '" is a file for user ' . self::getSystemUser(), LOGGER_DEBUG);
return false;
}
if (!is_dir($directory)) {
logger('Path "' . $directory . '" is not a directory for user ' . self::systemuser(), LOGGER_DEBUG);
logger('Path "' . $directory . '" is not a directory for user ' . self::getSystemUser(), LOGGER_DEBUG);
return false;
}
if ($check_writable && !is_writable($directory)) {
logger('Path "' . $directory . '" is not writable for user ' . self::systemuser(), LOGGER_DEBUG);
logger('Path "' . $directory . '" is not writable for user ' . self::getSystemUser(), LOGGER_DEBUG);
return false;
}
@ -1222,6 +1335,8 @@ class App
* @param string $cat Config category
* @param string $k Config key
* @param mixed $default Default value if it isn't set
*
* @return string Returns the value of the Config entry
*/
public function getConfigValue($cat, $k, $default = null)
{
@ -1240,6 +1355,20 @@ class App
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 setDefaultConfigValue($cat, $k, $v)
{
if (!isset($this->config[$cat][$k])) {
$this->setConfigValue($cat, $k, $v);
}
}
/**
* Sets a value in the config cache. Accepts raw output from the config table
*
@ -1290,6 +1419,8 @@ class App
* @param string $cat Config category
* @param string $k Config key
* @param mixed $default Default value if key isn't set
*
* @return string The value of the config entry
*/
public function getPConfigValue($uid, $cat, $k, $default = null)
{
@ -1351,7 +1482,7 @@ class App
{
$sender_email = Config::get('config', 'sender_email');
if (empty($sender_email)) {
$hostname = $this->get_hostname();
$hostname = $this->getHostName();
if (strpos($hostname, ':')) {
$hostname = substr($hostname, 0, strpos($hostname, ':'));
}
@ -1365,11 +1496,11 @@ class App
/**
* Returns the current theme name.
*
* @return string
* @return string the name of the current theme
*/
public function getCurrentTheme()
{
if ($this->isInstallMode()) {
if ($this->getMode()->isInstall()) {
return '';
}
@ -1378,7 +1509,7 @@ class App
/// https://github.com/friendica/friendica/issues/5092)
$this->computeCurrentTheme();
return $this->current_theme;
return $this->currentTheme;
}
/**
@ -1394,7 +1525,7 @@ class App
}
// Sane default
$this->current_theme = $system_theme;
$this->currentTheme = $system_theme;
$allowed_themes = explode(',', Config::get('system', 'allowed_themes', $system_theme));
@ -1433,7 +1564,7 @@ class App
&& (file_exists('view/theme/' . $theme_name . '/style.css')
|| file_exists('view/theme/' . $theme_name . '/style.php'))
) {
$this->current_theme = $theme_name;
$this->currentTheme = $theme_name;
}
}
@ -1448,4 +1579,32 @@ class App
{
return Core\Theme::getStylesheetPath($this->getCurrentTheme());
}
/**
* Check if request was an AJAX (xmlhttprequest) request.
*
* @return boolean true if it was an AJAX request
*/
public function isAjax()
{
return $this->isAjax;
}
/**
* Returns the value of a argv key
* TODO there are a lot of $a->argv usages in combination with defaults() which can be replaced with this method
*
* @param int $position the position of the argument
* @param mixed $default the default value if not found
*
* @return mixed returns the value of the argument
*/
public function getArgumentValue($position, $default = '')
{
if (array_key_exists($position, $this->argv)) {
return $this->argv[$position];
}
return $default;
}
}

117
src/App/Mode.php Normal file
View file

@ -0,0 +1,117 @@
<?php
namespace Friendica\App;
use Friendica\Core\Config;
use Friendica\Database\DBA;
/**
* Mode of the current Friendica Node
*
* @package Friendica\App
*/
class Mode
{
const LOCALCONFIGPRESENT = 1;
const DBAVAILABLE = 2;
const DBCONFIGAVAILABLE = 4;
const MAINTENANCEDISABLED = 8;
/***
* @var int the mode of this Application
*
*/
private $mode;
/**
* @var string the basepath of the application
*/
private $basepath;
public function __construct($basepath = '')
{
$this->basepath = $basepath;
$this->mode = 0;
}
/**
* Sets the App mode
*
* - App::MODE_INSTALL : Either the database connection can't be established or the config table doesn't exist
* - App::MODE_MAINTENANCE: The maintenance mode has been set
* - App::MODE_NORMAL : Normal run with all features enabled
*
* @param string $basepath the Basepath of the Application
*
*/
public function determine($basepath = null)
{
if (!empty($basepath)) {
$this->basepath = $basepath;
}
$this->mode = 0;
if (!file_exists($this->basepath . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'local.ini.php')
&& !file_exists($this->basepath . DIRECTORY_SEPARATOR . '.htconfig.php')) {
return;
}
$this->mode |= Mode::LOCALCONFIGPRESENT;
if (!DBA::connected()) {
return;
}
$this->mode |= Mode::DBAVAILABLE;
if (DBA::fetchFirst("SHOW TABLES LIKE 'config'") === false) {
return;
}
$this->mode |= Mode::DBCONFIGAVAILABLE;
if (Config::get('system', 'maintenance')) {
return;
}
$this->mode |= Mode::MAINTENANCEDISABLED;
}
/**
* Checks, if the Friendica Node has the given mode
*
* @param int $mode A mode to test
*
* @return bool returns true, if the mode is set
*/
public function has($mode)
{
return ($this->mode & $mode) > 0;
}
/**
* Install mode is when the local config file is missing or the DB schema hasn't been installed yet.
*
* @return bool
*/
public function isInstall()
{
return !$this->has(Mode::LOCALCONFIGPRESENT) ||
!$this->has(MODE::DBCONFIGAVAILABLE);
}
/**
* Normal mode is when the local config file is set, the DB schema is installed and the maintenance mode is off.
*
* @return bool
*/
public function isNormal()
{
return $this->has(Mode::LOCALCONFIGPRESENT) &&
$this->has(Mode::DBAVAILABLE) &&
$this->has(Mode::DBCONFIGAVAILABLE) &&
$this->has(Mode::MAINTENANCEDISABLED);
}
}

View file

@ -9,7 +9,7 @@ namespace Friendica;
* The filename of the module in src/Module needs to match the class name
* exactly to make the module available.
*
* @author Hypolite Petovan mrpetovan@gmail.com
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
abstract class BaseModule extends BaseObject
{
@ -21,7 +21,16 @@ abstract class BaseModule extends BaseObject
*/
public static function init()
{
}
/**
* @brief Module GET method to display raw content from technical endpoints
*
* Extend this method if the module is supposed to return communication data,
* e.g. from protocol implementations.
*/
public static function rawContent()
{
}
/**

View file

@ -7,7 +7,9 @@ namespace Friendica\Content;
use Friendica\Core\Addon;
use Friendica\Core\L10n;
use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Util\Network;
/**
* @brief ContactSelector class
@ -68,28 +70,29 @@ class ContactSelector
}
/**
* @param string $s network
* @param string $network network
* @param string $profile optional, default empty
* @return string
*/
public static function networkToName($s, $profile = "")
public static function networkToName($network, $profile = "")
{
$nets = [
Protocol::DFRN => L10n::t('Friendica'),
Protocol::OSTATUS => L10n::t('OStatus'),
Protocol::FEED => L10n::t('RSS/Atom'),
Protocol::MAIL => L10n::t('Email'),
Protocol::DIASPORA => L10n::t('Diaspora'),
Protocol::ZOT => L10n::t('Zot!'),
Protocol::LINKEDIN => L10n::t('LinkedIn'),
Protocol::XMPP => L10n::t('XMPP/IM'),
Protocol::MYSPACE => L10n::t('MySpace'),
Protocol::GPLUS => L10n::t('Google+'),
Protocol::PUMPIO => L10n::t('pump.io'),
Protocol::TWITTER => L10n::t('Twitter'),
Protocol::DIASPORA2 => L10n::t('Diaspora Connector'),
Protocol::STATUSNET => L10n::t('GNU Social Connector'),
Protocol::PNUT => L10n::t('pnut'),
Protocol::DFRN => L10n::t('Friendica'),
Protocol::OSTATUS => L10n::t('OStatus'),
Protocol::FEED => L10n::t('RSS/Atom'),
Protocol::MAIL => L10n::t('Email'),
Protocol::DIASPORA => L10n::t('Diaspora'),
Protocol::ZOT => L10n::t('Zot!'),
Protocol::LINKEDIN => L10n::t('LinkedIn'),
Protocol::XMPP => L10n::t('XMPP/IM'),
Protocol::MYSPACE => L10n::t('MySpace'),
Protocol::GPLUS => L10n::t('Google+'),
Protocol::PUMPIO => L10n::t('pump.io'),
Protocol::TWITTER => L10n::t('Twitter'),
Protocol::DIASPORA2 => L10n::t('Diaspora Connector'),
Protocol::STATUSNET => L10n::t('GNU Social Connector'),
Protocol::ACTIVITYPUB => L10n::t('ActivityPub'),
Protocol::PNUT => L10n::t('pnut'),
];
Addon::callHooks('network_to_name', $nets);
@ -97,15 +100,37 @@ class ContactSelector
$search = array_keys($nets);
$replace = array_values($nets);
$networkname = str_replace($search, $replace, $s);
$networkname = str_replace($search, $replace, $network);
if ((in_array($s, [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) && ($profile != "")) {
$r = DBA::fetchFirst("SELECT `gserver`.`platform` FROM `gcontact`
INNER JOIN `gserver` ON `gserver`.`nurl` = `gcontact`.`server_url`
WHERE `gcontact`.`nurl` = ? AND `platform` != ''", normalise_link($profile));
if ((in_array($network, [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) && ($profile != "")) {
// Create the server url out of the profile url
$parts = parse_url($profile);
unset($parts['path']);
$server_url = [normalise_link(Network::unparseURL($parts))];
if (DBA::isResult($r)) {
$networkname = $r['platform'];
// Fetch the server url
$gcontact = DBA::selectFirst('gcontact', ['server_url'], ['nurl' => normalise_link($profile)]);
if (!empty($gcontact) && !empty($gcontact['server_url'])) {
$server_url[] = normalise_link($gcontact['server_url']);
}
// Now query the GServer for the platform name
$gserver = DBA::selectFirst('gserver', ['platform', 'network'], ['nurl' => $server_url]);
if (DBA::isResult($gserver)) {
if (!empty($gserver['platform'])) {
$platform = $gserver['platform'];
} elseif (!empty($gserver['network']) && ($gserver['network'] != Protocol::ACTIVITYPUB)) {
$platform = self::networkToName($gserver['network']);
}
if (!empty($platform)) {
$networkname = $platform;
if ($network == Protocol::ACTIVITYPUB) {
$networkname .= ' (AP)';
}
}
}
}

View file

@ -107,7 +107,7 @@ class Nav
// user info
$contact = DBA::selectFirst('contact', ['micro'], ['uid' => $a->user['uid'], 'self' => true]);
$userinfo = [
'icon' => (DBA::isResult($contact) ? $a->remove_baseurl($contact['micro']) : 'images/person-48.jpg'),
'icon' => (DBA::isResult($contact) ? $a->removeBaseURL($contact['micro']) : 'images/person-48.jpg'),
'name' => $a->user['username'],
];
} else {
@ -210,7 +210,7 @@ class Nav
$nav['profiles'] = ['profiles', L10n::t('Profiles'), '', L10n::t('Manage/Edit Profiles')];
}
$nav['contacts'] = ['contacts', L10n::t('Contacts'), '', L10n::t('Manage/edit friends and contacts')];
$nav['contacts'] = ['contact', L10n::t('Contacts'), '', L10n::t('Manage/edit friends and contacts')];
}
// Show the link to the admin configuration page if user is admin

View file

@ -31,7 +31,7 @@ require_once 'include/dba.php';
*
* @see https://oembed.com
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class OEmbed
{

View file

@ -25,7 +25,6 @@ use Friendica\Util\Map;
use Friendica\Util\Network;
use Friendica\Util\ParseUrl;
use Friendica\Util\Proxy as ProxyUtils;
use League\HTMLToMarkdown\HtmlConverter;
class BBCode extends BaseObject
{
@ -348,7 +347,7 @@ class BBCode extends BaseObject
*/
public static function toPlaintext($text, $keep_urls = true)
{
$naked_text = preg_replace('/\[(.+?)\]/','', $text);
$naked_text = preg_replace('/\[(.+?)\]\s*/','', $text);
if (!$keep_urls) {
$naked_text = preg_replace('#https?\://[^\s<]+[^\s\.\)]#i', '', $naked_text);
}
@ -572,16 +571,17 @@ class BBCode extends BaseObject
$return = sprintf('<div class="type-%s">', $data["type"]);
}
if (!empty($data["image"])) {
$return .= sprintf('<a href="%s" target="_blank"><img src="%s" alt="" title="%s" class="attachment-image" /></a><br />', $data["url"], self::proxyUrl($data["image"], $simplehtml), $data["title"]);
} elseif (!empty($data["preview"])) {
$return .= sprintf('<a href="%s" target="_blank"><img src="%s" alt="" title="%s" class="attachment-preview" /></a><br />', $data["url"], self::proxyUrl($data["preview"], $simplehtml), $data["title"]);
}
if (($data["type"] == "photo") && !empty($data["url"]) && !empty($data["image"])) {
$return .= sprintf('<a href="%s" target="_blank"><img src="%s" alt="" title="%s" class="attachment-image" /></a>', $data["url"], self::proxyUrl($data["image"], $simplehtml), $data["title"]);
} else {
$return .= sprintf('<h4><a href="%s">%s</a></h4>', $data['url'], $data['title']);
if (!empty($data['title']) && !empty($data['url'])) {
if (!empty($data["image"]) && empty($data["text"]) && ($data["type"] == "photo")) {
$return .= sprintf('<a href="%s" target="_blank"><img src="%s" alt="" title="%s" class="attachment-image" /></a>', $data["url"], self::proxyUrl($data["image"], $simplehtml), $data["title"]);
} else {
if (!empty($data["image"])) {
$return .= sprintf('<a href="%s" target="_blank"><img src="%s" alt="" title="%s" class="attachment-image" /></a><br />', $data["url"], self::proxyUrl($data["image"], $simplehtml), $data["title"]);
} elseif (!empty($data["preview"])) {
$return .= sprintf('<a href="%s" target="_blank"><img src="%s" alt="" title="%s" class="attachment-preview" /></a><br />', $data["url"], self::proxyUrl($data["preview"], $simplehtml), $data["title"]);
}
$return .= sprintf('<h4><a href="%s">%s</a></h4>', $data['url'], $data['title']);
}
}
if (!empty($data["description"]) && $data["description"] != $data["title"]) {
@ -589,7 +589,8 @@ class BBCode extends BaseObject
$bbcode = HTML::toBBCode($data["description"]);
$return .= sprintf('<blockquote>%s</blockquote>', trim(self::convert($bbcode)));
}
if ($data["type"] == "link") {
if (!empty($data['url'])) {
$return .= sprintf('<sup><a href="%s">%s</a></sup>', $data['url'], parse_url($data['url'], PHP_URL_HOST));
}
@ -858,187 +859,140 @@ class BBCode extends BaseObject
}
/**
* Processes [share] tags
* This function converts a [share] block to text according to a provided callback function whose signature is:
*
* function(array $attributes, array $author_contact, string $content, boolean $is_quote_share): string
*
* Where:
* - $attributes is an array of attributes of the [share] block itself. Missing keys will be completed by the contact
* data lookup
* - $author_contact is a contact record array
* - $content is the inner content of the [share] block
* - $is_quote_share indicates whether there's any content before the [share] block
* - Return value is the string that should replace the [share] block in the provided text
*
* This function is intended to be used by addon connector to format a share block like the target network is expecting it.
*
* @param string $text A BBCode string
* @param callable $callback
* @return string The BBCode string with all [share] blocks replaced
*/
public static function convertShare($text, callable $callback)
{
$return = preg_replace_callback(
"/(.*?)\[share(.*?)\](.*?)\[\/share\]/ism",
function ($match) use ($callback) {
$attribute_string = $match[2];
$attributes = [];
foreach(['author', 'profile', 'avatar', 'link', 'posted'] as $field) {
preg_match("/$field=(['\"])(.+?)\\1/ism", $attribute_string, $matches);
$attributes[$field] = html_entity_decode(defaults($matches, 2, ''), ENT_QUOTES, 'UTF-8');
}
// We only call this so that a previously unknown contact can be added.
// This is important for the function "Model\Contact::getDetailsByURL()".
// This function then can fetch an entry from the contact table.
Contact::getIdForURL($attributes['profile'], 0, true);
$author_contact = Contact::getDetailsByURL($attributes['profile']);
$author_contact['addr'] = defaults($author_contact, 'addr' , Protocol::getAddrFromProfileUrl($attributes['profile']));
$attributes['author'] = defaults($author_contact, 'name' , $attributes['author']);
$attributes['avatar'] = defaults($author_contact, 'micro', $attributes['avatar']);
$attributes['profile'] = defaults($author_contact, 'url' , $attributes['profile']);
if ($attributes['avatar']) {
$attributes['avatar'] = ProxyUtils::proxifyUrl($attributes['avatar'], false, ProxyUtils::SIZE_THUMB);
}
return $match[1] . $callback($attributes, $author_contact, $match[3], trim($match[1]) != '');
},
$text
);
return $return;
}
/**
* Default [share] tag conversion callback
*
* Note: Can produce a [bookmark] tag in the output
*
* @brief Processes [share] tags
* @param array $share preg_match_callback result array
* @param bool|int $simplehtml
* @see BBCode::convertShare()
* @param array $attributes [share] block attribute values
* @param array $author_contact Contact row of the shared author
* @param string $content Inner content of the [share] block
* @param boolean $is_quote_share Whether there is content before the [share] block
* @param integer $simplehtml Mysterious integer value depending on the target network/formatting style
* @return string
*/
private static function convertShare($share, $simplehtml)
private static function convertShareCallback(array $attributes, array $author_contact, $content, $is_quote_share, $simplehtml)
{
$attributes = $share[2];
$author = "";
preg_match("/author='(.*?)'/ism", $attributes, $matches);
if (x($matches, 1)) {
$author = html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8');
}
preg_match('/author="(.*?)"/ism', $attributes, $matches);
if (x($matches, 1)) {
$author = $matches[1];
}
$profile = "";
preg_match("/profile='(.*?)'/ism", $attributes, $matches);
if (x($matches, 1)) {
$profile = $matches[1];
}
preg_match('/profile="(.*?)"/ism', $attributes, $matches);
if (x($matches, 1)) {
$profile = $matches[1];
}
$avatar = "";
preg_match("/avatar='(.*?)'/ism", $attributes, $matches);
if (x($matches, 1)) {
$avatar = $matches[1];
}
preg_match('/avatar="(.*?)"/ism', $attributes, $matches);
if (x($matches, 1)) {
$avatar = $matches[1];
}
$link = "";
preg_match("/link='(.*?)'/ism", $attributes, $matches);
if (x($matches, 1)) {
$link = $matches[1];
}
preg_match('/link="(.*?)"/ism', $attributes, $matches);
if (x($matches, 1)) {
$link = $matches[1];
}
$posted = "";
preg_match("/posted='(.*?)'/ism", $attributes, $matches);
if (x($matches, 1)) {
$posted = $matches[1];
}
preg_match('/posted="(.*?)"/ism', $attributes, $matches);
if (x($matches, 1)) {
$posted = $matches[1];
}
// We only call this so that a previously unknown contact can be added.
// This is important for the function "Model\Contact::getDetailsByURL()".
// This function then can fetch an entry from the contact table.
Contact::getIdForURL($profile, 0, true);
$data = Contact::getDetailsByURL($profile);
if (x($data, "name") && x($data, "addr")) {
$userid_compact = $data["name"] . " (" . $data["addr"] . ")";
} else {
$userid_compact = Protocol::getAddrFromProfileUrl($profile, $author);
}
if (x($data, "addr")) {
$userid = $data["addr"];
} else {
$userid = Protocol::formatMention($profile, $author);
}
if (x($data, "name")) {
$author = $data["name"];
}
if (x($data, "micro")) {
$avatar = $data["micro"];
}
$preshare = trim($share[1]);
if ($preshare != "") {
$preshare .= "<br />";
}
$mention = Protocol::formatMention($attributes['profile'], $attributes['author']);
switch ($simplehtml) {
case 1:
$text = $preshare . html_entity_decode("&#x2672; ", ENT_QUOTES, 'UTF-8') . ' <a href="' . $profile . '">' . $userid . "</a>: <br />»" . $share[3] . "«";
$text = ($is_quote_share? '<br />' : '') . '<p>' . html_entity_decode('&#x2672; ', ENT_QUOTES, 'UTF-8') . ' <a href="' . $attributes['profile'] . '">' . $mention . '</a>: </p>' . "\n" . '«' . $content . '»';
break;
case 2:
$text = $preshare . html_entity_decode("&#x2672; ", ENT_QUOTES, 'UTF-8') . ' ' . $userid_compact . ": <br />" . $share[3];
$text = ($is_quote_share? '<br />' : '') . '<p>' . html_entity_decode('&#x2672; ', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ': </p>' . "\n" . $content;
break;
case 3: // Diaspora
$headline = '<b>' . html_entity_decode("&#x2672; ", ENT_QUOTES, 'UTF-8') . $userid . ':</b><br />';
$headline = '<p><b>' . html_entity_decode('&#x2672; ', ENT_QUOTES, 'UTF-8') . $mention . ':</b></p>' . "\n";
$text = trim($share[1]);
if ($text != "") {
$text .= "<hr />";
}
if (stripos(normalise_link($link), 'http://twitter.com/') === 0) {
$text .= '<br /><a href="' . $link . '">' . $link . '</a>';
if (stripos(normalise_link($attributes['link']), 'http://twitter.com/') === 0) {
$text = ($is_quote_share? '<hr />' : '') . '<p><a href="' . $attributes['link'] . '">' . $attributes['link'] . '</a></p>' . "\n";
} else {
$text .= $headline . '<blockquote>' . trim($share[3]) . "</blockquote><br />";
$text = ($is_quote_share? '<hr />' : '') . $headline . '<blockquote>' . trim($content) . '</blockquote>' . "\n";
if ($link != "") {
$text .= '<br /><a href="' . $link . '">[l]</a>';
if ($attributes['link'] != '') {
$text .= '<p><a href="' . $attributes['link'] . '">[l]</a></p>' . "\n";
}
}
break;
case 4:
$headline = '<br /><b>' . html_entity_decode("&#x2672; ", ENT_QUOTES, 'UTF-8');
$headline .= L10n::t('<a href="%1$s" target="_blank">%2$s</a> %3$s', $link, $userid, $posted);
$headline .= ":</b><br />";
$headline = '<p><b>' . html_entity_decode('&#x2672; ', ENT_QUOTES, 'UTF-8');
$headline .= L10n::t('<a href="%1$s" target="_blank">%2$s</a> %3$s', $attributes['link'], $mention, $attributes['posted']);
$headline .= ':</b></p>' . "\n";
$text = trim($share[1]);
if ($text != "") {
$text .= "<hr />";
}
$text .= $headline . '<blockquote class="shared_content">' . trim($share[3]) . "</blockquote><br />";
$text = ($is_quote_share? '<hr />' : '') . $headline . '<blockquote class="shared_content">' . trim($content) . '</blockquote>' . "\n";
break;
case 5:
$text = $preshare . html_entity_decode("&#x2672; ", ENT_QUOTES, 'UTF-8') . ' ' . $userid_compact . ": <br />" . $share[3];
$text = ($is_quote_share? '<br />' : '') . '<p>' . html_entity_decode('&#x2672; ', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ': </p>' . "\n" . $content;
break;
case 7: // statusnet/GNU Social
$text = $preshare . html_entity_decode("&#x2672; ", ENT_QUOTES, 'UTF-8') . " @" . $userid_compact . ": " . $share[3];
break;
case 8: // twitter
$text = $preshare . "RT @" . $userid_compact . ": " . $share[3];
$text = ($is_quote_share? '<br />' : '') . '<p>' . html_entity_decode('&#x2672; ', ENT_QUOTES, 'UTF-8') . ' @' . $author_contact['addr'] . ': ' . $content . '</p>' . "\n";
break;
case 9: // Google+
$text = $preshare . html_entity_decode("&#x2672; ", ENT_QUOTES, 'UTF-8') . ' ' . $userid_compact . ": <br />" . $share[3];
$text = ($is_quote_share? '<br />' : '') . '<p>' . html_entity_decode('&#x2672; ', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ': </p>' . "\n";
$text .= '<p>' . $content . '</p>' . "\n";
if ($link != "") {
$text .= "<br /><br />" . $link;
if ($attributes['link'] != '') {
$text .= '<p>' . $attributes['link'] . '</p>';
}
break;
default:
// Transforms quoted tweets in rich attachments to avoid nested tweets
if (stripos(normalise_link($link), 'http://twitter.com/') === 0 && OEmbed::isAllowedURL($link)) {
if (stripos(normalise_link($attributes['link']), 'http://twitter.com/') === 0 && OEmbed::isAllowedURL($attributes['link'])) {
try {
$oembed = OEmbed::getHTML($link, $preshare);
$text = ($is_quote_share? '<br />' : '') . OEmbed::getHTML($attributes['link']);
} catch (Exception $e) {
$oembed = sprintf('[bookmark=%s]%s[/bookmark]', $link, $preshare);
$text = ($is_quote_share? '<br />' : '') . sprintf('[bookmark=%s]%s[/bookmark]', $attributes['link'], $content);
}
$text = $preshare . $oembed;
} else {
$text = trim($share[1]) . "\n";
$avatar = ProxyUtils::proxifyUrl($avatar, false, ProxyUtils::SIZE_THUMB);
$text = ($is_quote_share? "\n" : '');
$tpl = get_markup_template('shared_content.tpl');
$text .= replace_macros($tpl, [
'$profile' => $profile,
'$avatar' => $avatar,
'$author' => $author,
'$link' => $link,
'$posted' => $posted,
'$content' => trim($share[3])
'$profile' => $attributes['profile'],
'$avatar' => $attributes['avatar'],
'$author' => $attributes['author'],
'$link' => $attributes['link'],
'$posted' => $attributes['posted'],
'$content' => trim($content)
]);
}
break;
@ -1059,11 +1013,11 @@ class BBCode extends BaseObject
$ch = @curl_init($match[1]);
@curl_setopt($ch, CURLOPT_NOBODY, true);
@curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
@curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
@curl_setopt($ch, CURLOPT_USERAGENT, $a->getUserAgent());
@curl_exec($ch);
$curl_info = @curl_getinfo($ch);
$a->save_timestamp($stamp1, "network");
$a->saveTimestamp($stamp1, "network");
if (substr($curl_info["content_type"], 0, 6) == "image/") {
$text = "[url=" . $match[1] . "]" . $match[1] . "[/url]";
@ -1118,11 +1072,11 @@ class BBCode extends BaseObject
$ch = @curl_init($match[1]);
@curl_setopt($ch, CURLOPT_NOBODY, true);
@curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
@curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
@curl_setopt($ch, CURLOPT_USERAGENT, $a->getUserAgent());
@curl_exec($ch);
$curl_info = @curl_getinfo($ch);
$a->save_timestamp($stamp1, "network");
$a->saveTimestamp($stamp1, "network");
// if its a link to a picture then embed this picture
if (substr($curl_info["content_type"], 0, 6) == "image/") {
@ -1162,21 +1116,6 @@ class BBCode extends BaseObject
return $return;
}
private static function textHighlightCallback($match)
{
// Fallback in case the language doesn't exist
$return = '[code]' . $match[2] . '[/code]';
if (in_array(strtolower($match[1]),
['php', 'css', 'mysql', 'sql', 'abap', 'diff', 'html', 'perl', 'ruby',
'vbscript', 'avrc', 'dtd', 'java', 'xml', 'cpp', 'python', 'javascript', 'js', 'sh', 'bash'])
) {
$return = text_highlight($match[2], strtolower($match[1]));
}
return $return;
}
/**
* @brief Converts a BBCode message to HTML message
*
@ -1225,6 +1164,22 @@ class BBCode extends BaseObject
return $return;
};
// Extracting multi-line code blocks before the whitespace processing
$codeblocks = [];
$text = preg_replace_callback("#\[code(?:=([^\]]*))?\](.*?)\[\/code\]#is",
function ($matches) use (&$codeblocks) {
$return = $matches[0];
if (strpos($matches[2], "\n") !== false) {
$return = '#codeblock-' . count($codeblocks) . '#';
$codeblocks[] = '<pre><code class="language-' . trim($matches[1]) . '">' . trim($matches[2], "\n\r") . '</code></pre>';
}
return $return;
},
$text
);
// Hide all [noparse] contained bbtags by spacefying them
// POSSIBLE BUG --> Will the 'preg' functions crash if there's an embedded image?
@ -1263,19 +1218,11 @@ class BBCode extends BaseObject
$text = preg_replace("/\s?\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "[share$1]$2[/share]", $text);
$text = preg_replace("/\s?\[quote(.*?)\]\s?(.*?)\s?\[\/quote\]\s?/ism", "[quote$1]$2[/quote]", $text);
$text = preg_replace("/\n\[code\]/ism", "[code]", $text);
$text = preg_replace("/\[\/code\]\n/ism", "[/code]", $text);
// when the content is meant exporting to other systems then remove the avatar picture since this doesn't really look good on these systems
if (!$try_oembed) {
$text = preg_replace("/\[share(.*?)avatar\s?=\s?'.*?'\s?(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "\n[share$1$2]$3[/share]", $text);
}
// Check for [code] text here, before the linefeeds are messed with.
// The highlighter will unescape and re-escape the content.
if (strpos($text, '[code=') !== false) {
$text = preg_replace_callback("/\[code=(.*?)\](.*?)\[\/code\]/ism", 'self::textHighlightCallback', $text);
}
// Convert new line chars to html <br /> tags
// nlbr seems to be hopelessly messed up
@ -1627,10 +1574,12 @@ class BBCode extends BaseObject
$text = preg_replace("/\[zmg\](.*?)\[\/zmg\]/ism", '<img src="$1" alt="' . L10n::t('Image/photo') . '" />', $text);
// Shared content
$text = preg_replace_callback("/(.*?)\[share(.*?)\](.*?)\[\/share\]/ism",
function ($match) use ($simple_html) {
return self::convertShare($match, $simple_html);
}, $text);
$text = self::convertShare(
$text,
function (array $attributes, array $author_contact, $content, $is_quote_share) use ($simple_html) {
return self::convertShareCallback($attributes, $author_contact, $content, $is_quote_share, $simple_html);
}
);
$text = preg_replace("/\[crypt\](.*?)\[\/crypt\]/ism", '<br/><img src="' .System::baseUrl() . '/images/lock_icon.gif" alt="' . L10n::t('Encrypted content') . '" title="' . L10n::t('Encrypted content') . '" /><br />', $text);
$text = preg_replace("/\[crypt(.*?)\](.*?)\[\/crypt\]/ism", '<br/><img src="' .System::baseUrl() . '/images/lock_icon.gif" alt="' . L10n::t('Encrypted content') . '" title="' . '$1' . ' ' . L10n::t('Encrypted content') . '" /><br />', $text);
@ -1720,18 +1669,6 @@ class BBCode extends BaseObject
$text = Smilies::replace($text, false, true);
}
// Replace inline code blocks
$text = preg_replace_callback("|(?!<br[^>]*>)<code>([^<]*)</code>(?!<br[^>]*>)|ism",
function ($match) use ($simple_html) {
$return = '<key>' . $match[1] . '</key>';
// Use <code> for Diaspora inline code blocks
if ($simple_html === 3) {
$return = '<code>' . $match[1] . '</code>';
}
return $return;
}
, $text);
// Unhide all [noparse] contained bbtags unspacefying them
// and triming the [noparse] tag.
@ -1769,6 +1706,18 @@ class BBCode extends BaseObject
$text = self::interpolateSavedImagesIntoItemBody($text, $saved_image);
}
// Restore code blocks
$text = preg_replace_callback('/#codeblock-([0-9]+)#/iU',
function ($matches) use ($codeblocks) {
$return = $matches[0];
if (isset($codeblocks[intval($matches[1])])) {
$return = $codeblocks[$matches[1]];
}
return $return;
},
$text
);
// Clean up the HTML by loading and saving the HTML with the DOM.
// Bad structured html can break a whole page.
// For performance reasons do it only with ativated item cache or at export.
@ -1903,23 +1852,6 @@ class BBCode extends BaseObject
// Converting images with size parameters to simple images. Markdown doesn't know it.
$text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $text);
// Extracting multi-line code blocks before the whitespace processing/code highlighter in self::convert()
$codeblocks = [];
$text = preg_replace_callback("#\[code(?:=([^\]]*))?\](.*?)\[\/code\]#is",
function ($matches) use (&$codeblocks) {
$return = $matches[0];
if (strpos($matches[2], "\n") !== false) {
$return = '#codeblock-' . count($codeblocks) . '#';
$prefix = '````' . $matches[1] . PHP_EOL;
$codeblocks[] = $prefix . trim($matches[2]) . PHP_EOL . '````';
}
return $return;
},
$text
);
// Convert it to HTML - don't try oembed
if ($for_diaspora) {
$text = self::convert($text, false, 3);
@ -1949,13 +1881,12 @@ class BBCode extends BaseObject
$stamp1 = microtime(true);
// Now convert HTML to Markdown
$converter = new HtmlConverter();
$text = $converter->convert($text);
$text = HTML::toMarkdown($text);
// unmask the special chars back to HTML
$text = str_replace(['&\_lt\_;', '&\_gt\_;', '&\_amp\_;'], ['&lt;', '&gt;', '&amp;'], $text);
$a->save_timestamp($stamp1, "parser");
$a->saveTimestamp($stamp1, "parser");
// Libertree has a problem with escaped hashtags.
$text = str_replace(['\#'], ['#'], $text);
@ -1973,18 +1904,6 @@ class BBCode extends BaseObject
);
}
// Restore code blocks
$text = preg_replace_callback('/#codeblock-([0-9]+)#/iU',
function ($matches) use ($codeblocks) {
$return = '';
if (isset($codeblocks[intval($matches[1])])) {
$return = $codeblocks[$matches[1]];
}
return $return;
},
$text
);
Addon::callHooks('bb2diaspora', $text);
return $text;

View file

@ -11,6 +11,7 @@ use DOMXPath;
use Friendica\Core\Addon;
use Friendica\Util\Network;
use Friendica\Util\XML;
use League\HTMLToMarkdown\HtmlConverter;
class HTML
{
@ -122,7 +123,7 @@ class HTML
// Removing code blocks before the whitespace removal processing below
$codeblocks = [];
$message = preg_replace_callback(
'#<pre><code(?: class="([^"]*)")?>(.*)</code></pre>#iUs',
'#<pre><code(?: class="language-([^"]*)")?>(.*)</code></pre>#iUs',
function ($matches) use (&$codeblocks) {
$return = '[codeblock-' . count($codeblocks) . ']';
@ -131,7 +132,7 @@ class HTML
$prefix = '[code=' . $matches[1] . ']';
}
$codeblocks[] = $prefix . trim($matches[2]) . '[/code]';
$codeblocks[] = $prefix . PHP_EOL . trim($matches[2]) . PHP_EOL . '[/code]';
return $return;
},
$message
@ -672,4 +673,19 @@ class HTML
return trim($message);
}
/**
* Converts provided HTML code to Markdown. The hardwrap parameter maximizes
* compatibility with Diaspora in spite of the Markdown standards.
*
* @param string $html
* @return string
*/
public static function toMarkdown($html)
{
$converter = new HtmlConverter(['hard_break' => true]);
$markdown = $converter->convert($html);
return $markdown;
}
}

View file

@ -14,7 +14,7 @@ use Friendica\Content\Text\HTML;
/**
* Friendica-specific usage of Markdown
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class Markdown extends BaseObject
{
@ -32,9 +32,10 @@ class Markdown extends BaseObject
$MarkdownParser = new MarkdownExtra();
$MarkdownParser->hard_wrap = $hardwrap;
$MarkdownParser->code_class_prefix = 'language-';
$html = $MarkdownParser->transform($text);
self::getApp()->save_timestamp($stamp1, "parser");
self::getApp()->saveTimestamp($stamp1, "parser");
return $html;
}

View file

@ -142,10 +142,7 @@ class Widget
$nets = array();
while ($rr = DBA::fetch($r)) {
/// @TODO If 'network' is not there, this triggers an E_NOTICE
if ($rr['network']) {
$nets[] = array('ref' => $rr['network'], 'name' => ContactSelector::networkToName($rr['network']), 'selected' => (($selected == $rr['network']) ? 'selected' : '' ));
}
$nets[] = array('ref' => $rr['network'], 'name' => ContactSelector::networkToName($rr['network']), 'selected' => (($selected == $rr['network']) ? 'selected' : '' ));
}
DBA::close($r);

View file

@ -17,7 +17,7 @@ use Friendica\Util\Network;
/**
* Handle ACL management and display
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class ACL extends BaseObject
{
@ -51,14 +51,14 @@ class ACL extends BaseObject
break;
case 'PRIVATE':
$networks = [Protocol::DFRN, Protocol::MAIL, Protocol::DIASPORA];
$networks = [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::MAIL, Protocol::DIASPORA];
break;
case 'TWO_WAY':
if (!empty($a->user['prvnets'])) {
$networks = [Protocol::DFRN, Protocol::MAIL, Protocol::DIASPORA];
$networks = [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::MAIL, Protocol::DIASPORA];
} else {
$networks = [Protocol::DFRN, Protocol::MAIL, Protocol::DIASPORA, Protocol::OSTATUS];
$networks = [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::MAIL, Protocol::DIASPORA, Protocol::OSTATUS];
}
break;
@ -344,9 +344,9 @@ class ACL extends BaseObject
$a = self::getApp();
$p = $a->pager['page'] != 1 ? '&p=' . $a->pager['page'] : '';
$response = Network::curl(get_server() . '/lsearch?f=' . $p . '&search=' . urlencode($search));
if ($response['success']) {
$lsearch = json_decode($response['body'], true);
$curlResult = Network::curl(get_server() . '/lsearch?f=' . $p . '&search=' . urlencode($search));
if ($curlResult->isSuccess()) {
$lsearch = json_decode($curlResult->getBody(), true);
if (!empty($lsearch['results'])) {
$return = $lsearch['results'];
}

View file

@ -5,6 +5,7 @@
namespace Friendica\Core;
use Friendica\App;
use Friendica\BaseObject;
use Friendica\Database\DBA;
require_once 'include/dba.php';
@ -12,8 +13,65 @@ require_once 'include/dba.php';
/**
* Some functions to handle addons
*/
class Addon
class Addon extends BaseObject
{
/**
* @brief Synchronise addons:
*
* system.addon contains a comma-separated list of names
* of addons which are used on this system.
* Go through the database list of already installed addons, and if we have
* an entry, but it isn't in the config list, call the uninstall procedure
* and mark it uninstalled in the database (for now we'll remove it).
* Then go through the config list and if we have a addon that isn't installed,
* call the install procedure and add it to the database.
*
*/
public static function check()
{
$a = self::getApp();
$r = DBA::select('addon', [], ['installed' => 1]);
if (DBA::isResult($r)) {
$installed = DBA::toArray($r);
} else {
$installed = [];
}
$addons = Config::get('system', 'addon');
$addons_arr = [];
if ($addons) {
$addons_arr = explode(',', str_replace(' ', '', $addons));
}
$a->addons = $addons_arr;
$installed_arr = [];
if (count($installed)) {
foreach ($installed as $i) {
if (!in_array($i['name'], $addons_arr)) {
self::uninstall($i['name']);
} else {
$installed_arr[] = $i['name'];
}
}
}
if (count($addons_arr)) {
foreach ($addons_arr as $p) {
if (!in_array($p, $installed_arr)) {
self::install($p);
}
}
}
self::loadHooks();
return;
}
/**
* @brief uninstalls an addon.
*
@ -139,7 +197,7 @@ class Addon
*/
public static function registerHook($hook, $file, $function, $priority = 0)
{
$file = str_replace(get_app()->get_basepath() . DIRECTORY_SEPARATOR, '', $file);
$file = str_replace(self::getApp()->getBasePath() . DIRECTORY_SEPARATOR, '', $file);
$condition = ['hook' => $hook, 'file' => $file, 'function' => $function];
$exists = DBA::exists('hook', $condition);
@ -162,7 +220,7 @@ class Addon
*/
public static function unregisterHook($hook, $file, $function)
{
$relative_file = str_replace(get_app()->get_basepath() . DIRECTORY_SEPARATOR, '', $file);
$relative_file = str_replace(self::getApp()->getBasePath() . DIRECTORY_SEPARATOR, '', $file);
// This here is only needed for fixing a problem that existed on the develop branch
$condition = ['hook' => $hook, 'file' => $file, 'function' => $function];
@ -178,7 +236,7 @@ class Addon
*/
public static function loadHooks()
{
$a = get_app();
$a = self::getApp();
$a->hooks = [];
$r = DBA::select('hook', ['hook', 'file', 'function'], [], ['order' => ['priority' => 'desc', 'file']]);
@ -201,7 +259,7 @@ class Addon
*/
public static function forkHooks($priority, $name, $data = null)
{
$a = get_app();
$a = self::getApp();
if (is_array($a->hooks) && array_key_exists($name, $a->hooks)) {
foreach ($a->hooks[$name] as $hook) {
@ -221,7 +279,7 @@ class Addon
*/
public static function callHooks($name, &$data = null)
{
$a = get_app();
$a = self::getApp();
if (is_array($a->hooks) && array_key_exists($name, $a->hooks)) {
foreach ($a->hooks[$name] as $hook) {
@ -262,7 +320,7 @@ class Addon
*/
public static function isApp($name)
{
$a = get_app();
$a = self::getApp();
if (is_array($a->hooks) && (array_key_exists('app_menu', $a->hooks))) {
foreach ($a->hooks['app_menu'] as $hook) {
@ -293,7 +351,7 @@ class Addon
*/
public static function getInfo($addon)
{
$a = get_app();
$a = self::getApp();
$info = [
'name' => $addon,
@ -310,7 +368,7 @@ class Addon
$stamp1 = microtime(true);
$f = file_get_contents("addon/$addon/$addon.php");
$a->save_timestamp($stamp1, "file");
$a->saveTimestamp($stamp1, "file");
$r = preg_match("|/\*.*\*/|msU", $f, $m);

View file

@ -23,13 +23,15 @@ class Cache extends \Friendica\BaseObject
/**
* @var Cache\ICacheDriver
*/
private static $driver = null;
private static $driver = null;
public static $driver_class = null;
public static $driver_name = null;
public static function init()
{
$driver_name = Config::get('system', 'cache_driver', 'database');
self::$driver = CacheDriverFactory::create($driver_name);
self::$driver_name = Config::get('system', 'cache_driver', 'database');
self::$driver = CacheDriverFactory::create(self::$driver_name);
self::$driver_class = get_class(self::$driver);
}
/**
@ -46,6 +48,24 @@ class Cache extends \Friendica\BaseObject
return self::$driver;
}
/**
* @brief Returns all the cache keys sorted alphabetically
*
* @param string $prefix Prefix of the keys (optional)
*
* @return array Empty if the driver doesn't support this feature
*/
public static function getAllKeys($prefix = null)
{
$time = microtime(true);
$return = self::getDriver()->getAllKeys($prefix);
self::getApp()->saveTimestamp($time, 'cache');
return $return;
}
/**
* @brief Fetch cached data according to the key
*
@ -59,7 +79,7 @@ class Cache extends \Friendica\BaseObject
$return = self::getDriver()->get($key);
self::getApp()->save_timestamp($time, 'cache');
self::getApp()->saveTimestamp($time, 'cache');
return $return;
}
@ -81,7 +101,7 @@ class Cache extends \Friendica\BaseObject
$return = self::getDriver()->set($key, $value, $duration);
self::getApp()->save_timestamp($time, 'cache_write');
self::getApp()->saveTimestamp($time, 'cache_write');
return $return;
}
@ -99,7 +119,7 @@ class Cache extends \Friendica\BaseObject
$return = self::getDriver()->delete($key);
self::getApp()->save_timestamp($time, 'cache_write');
self::getApp()->saveTimestamp($time, 'cache_write');
return $return;
}
@ -107,12 +127,12 @@ class Cache extends \Friendica\BaseObject
/**
* @brief Remove outdated data from the cache
*
* @param integer $max_level The maximum cache level that is to be cleared
* @param boolean $outdated just remove outdated values
*
* @return void
*/
public static function clear()
public static function clear($outdated = true)
{
return self::getDriver()->clear();
return self::getDriver()->clear($outdated);
}
}

View file

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

View file

@ -19,6 +19,14 @@ class ArrayCache extends AbstractCacheDriver implements IMemoryCacheDriver
/** @var array Array with the cached data */
protected $cachedData = array();
/**
* (@inheritdoc)
*/
public function getAllKeys($prefix = null)
{
return $this->filterArrayKeysByPrefix($this->cachedData, $prefix);
}
/**
* (@inheritdoc)
*/
@ -53,6 +61,11 @@ class ArrayCache extends AbstractCacheDriver implements IMemoryCacheDriver
*/
public function clear($outdated = true)
{
// Array doesn't support TTL so just don't delete something
if ($outdated) {
return true;
}
$this->cachedData = [];
return true;
}

View file

@ -9,10 +9,35 @@ use Friendica\Util\DateTimeFormat;
/**
* Database Cache Driver
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class DatabaseCacheDriver extends AbstractCacheDriver implements ICacheDriver
{
/**
* (@inheritdoc)
*/
public function getAllKeys($prefix = null)
{
if (empty($prefix)) {
$where = ['`expires` >= ?', DateTimeFormat::utcNow()];
} else {
$where = ['`expires` >= ? AND `k` LIKE CONCAT(?, \'%\')', DateTimeFormat::utcNow(), $prefix];
}
$stmt = DBA::select('cache', ['k'], $where);
$keys = [];
while ($key = DBA::fetch($stmt)) {
array_push($keys, $key['k']);
}
DBA::close($stmt);
return $keys;
}
/**
* (@inheritdoc)
*/
public function get($key)
{
$cache = DBA::selectFirst('cache', ['v'], ['`k` = ? AND `expires` >= ?', $key, DateTimeFormat::utcNow()]);
@ -32,6 +57,9 @@ class DatabaseCacheDriver extends AbstractCacheDriver implements ICacheDriver
return null;
}
/**
* (@inheritdoc)
*/
public function set($key, $value, $ttl = Cache::FIVE_MINUTES)
{
$fields = [
@ -43,11 +71,17 @@ class DatabaseCacheDriver extends AbstractCacheDriver implements ICacheDriver
return DBA::update('cache', $fields, ['k' => $key], true);
}
/**
* (@inheritdoc)
*/
public function delete($key)
{
return DBA::delete('cache', ['k' => $key]);
}
/**
* (@inheritdoc)
*/
public function clear($outdated = true)
{
if ($outdated) {

View file

@ -7,10 +7,19 @@ use Friendica\Core\Cache;
/**
* Cache Driver Interface
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
interface ICacheDriver
{
/**
* Lists all cache keys
*
* @param string prefix optional a prefix to search
*
* @return array Empty if it isn't supported by the cache driver
*/
public function getAllKeys($prefix = null);
/**
* Fetches cached data according to the key
*

View file

@ -10,7 +10,7 @@ use Memcache;
/**
* Memcache Cache Driver
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class MemcacheCacheDriver extends AbstractCacheDriver implements IMemoryCacheDriver
{
@ -22,6 +22,11 @@ class MemcacheCacheDriver extends AbstractCacheDriver implements IMemoryCacheDri
*/
private $memcache;
/**
* @param string $memcache_host
* @param int $memcache_port
* @throws Exception
*/
public function __construct($memcache_host, $memcache_port)
{
if (!class_exists('Memcache', false)) {
@ -35,6 +40,30 @@ class MemcacheCacheDriver extends AbstractCacheDriver implements IMemoryCacheDri
}
}
/**
* (@inheritdoc)
*/
public function getAllKeys($prefix = null)
{
$keys = [];
$allSlabs = $this->memcache->getExtendedStats('slabs');
foreach ($allSlabs as $slabs) {
foreach (array_keys($slabs) as $slabId) {
$cachedump = $this->memcache->getExtendedStats('cachedump', (int)$slabId);
foreach ($cachedump as $key => $arrVal) {
if (!is_array($arrVal)) {
continue;
}
$keys = array_merge($keys, array_keys($arrVal));
}
}
}
$keys = $this->getOriginalKeys($keys);
return $this->filterArrayKeysByPrefix($keys, $prefix);
}
/**
* (@inheritdoc)
*/

View file

@ -5,12 +5,13 @@ namespace Friendica\Core\Cache;
use Friendica\Core\Cache;
use Exception;
use Friendica\Network\HTTPException\InternalServerErrorException;
use Memcached;
/**
* Memcached Cache Driver
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class MemcachedCacheDriver extends AbstractCacheDriver implements IMemoryCacheDriver
{
@ -53,6 +54,24 @@ class MemcachedCacheDriver extends AbstractCacheDriver implements IMemoryCacheDr
}
}
/**
* (@inheritdoc)
*/
public function getAllKeys($prefix = null)
{
$keys = $this->getOriginalKeys($this->memcached->getAllKeys());
if ($this->memcached->getResultCode() == Memcached::RES_SUCCESS) {
return $this->filterArrayKeysByPrefix($keys, $prefix);
} else {
logger('Memcached \'getAllKeys\' failed with ' . $this->memcached->getResultMessage(), LOGGER_ALL);
return [];
}
}
/**
* (@inheritdoc)
*/
public function get($key)
{
$return = null;
@ -63,11 +82,16 @@ class MemcachedCacheDriver extends AbstractCacheDriver implements IMemoryCacheDr
if ($this->memcached->getResultCode() === Memcached::RES_SUCCESS) {
$return = $value;
} else {
logger('Memcached \'get\' failed with ' . $this->memcached->getResultMessage(), LOGGER_ALL);
}
return $return;
}
/**
* (@inheritdoc)
*/
public function set($key, $value, $ttl = Cache::FIVE_MINUTES)
{
$cachekey = $this->getCacheKey($key);
@ -85,15 +109,20 @@ class MemcachedCacheDriver extends AbstractCacheDriver implements IMemoryCacheDr
$value
);
}
}
/**
* (@inheritdoc)
*/
public function delete($key)
{
$cachekey = $this->getCacheKey($key);
return $this->memcached->delete($cachekey);
}
/**
* (@inheritdoc)
*/
public function clear($outdated = true)
{
if ($outdated) {
@ -104,12 +133,7 @@ class MemcachedCacheDriver extends AbstractCacheDriver implements IMemoryCacheDr
}
/**
* @brief Sets a value if it's not already stored
*
* @param string $key The cache key
* @param mixed $value The old value we know from the cache
* @param int $ttl The cache lifespan, must be one of the Cache constants
* @return bool
* (@inheritdoc)
*/
public function add($key, $value, $ttl = Cache::FIVE_MINUTES)
{

View file

@ -10,7 +10,7 @@ use Redis;
/**
* Redis Cache Driver. This driver is based on Memcache driver
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
* @author Roland Haeder <roland@mxchange.org>
*/
class RedisCacheDriver extends AbstractCacheDriver implements IMemoryCacheDriver
@ -20,6 +20,11 @@ class RedisCacheDriver extends AbstractCacheDriver implements IMemoryCacheDriver
*/
private $redis;
/**
* @param string $redis_host
* @param int $redis_port
* @throws Exception
*/
public function __construct($redis_host, $redis_port)
{
if (!class_exists('Redis', false)) {
@ -33,6 +38,25 @@ class RedisCacheDriver extends AbstractCacheDriver implements IMemoryCacheDriver
}
}
/**
* (@inheritdoc)
*/
public function getAllKeys($prefix = null)
{
if (empty($prefix)) {
$search = '*';
} else {
$search = $prefix . '*';
}
$list = $this->redis->keys($this->getCacheKey($search));
return $this->getOriginalKeys($list);
}
/**
* (@inheritdoc)
*/
public function get($key)
{
$return = null;
@ -55,6 +79,9 @@ class RedisCacheDriver extends AbstractCacheDriver implements IMemoryCacheDriver
return $return;
}
/**
* (@inheritdoc)
*/
public function set($key, $value, $ttl = Cache::FIVE_MINUTES)
{
$cachekey = $this->getCacheKey($key);
@ -75,12 +102,18 @@ class RedisCacheDriver extends AbstractCacheDriver implements IMemoryCacheDriver
}
}
/**
* (@inheritdoc)
*/
public function delete($key)
{
$cachekey = $this->getCacheKey($key);
return ($this->redis->delete($cachekey) > 0);
}
/**
* (@inheritdoc)
*/
public function clear($outdated = true)
{
if ($outdated) {
@ -127,6 +160,7 @@ class RedisCacheDriver extends AbstractCacheDriver implements IMemoryCacheDriver
$this->redis->unwatch();
return false;
}
/**
* (@inheritdoc)
*/

View file

@ -31,7 +31,7 @@ class Config extends BaseObject
public static function init()
{
// Database isn't ready or populated yet
if (!(self::getApp()->mode & App::MODE_DBCONFIGAVAILABLE)) {
if (!self::getApp()->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
return;
}
@ -55,7 +55,7 @@ class Config extends BaseObject
public static function load($family = "config")
{
// Database isn't ready or populated yet
if (!(self::getApp()->mode & App::MODE_DBCONFIGAVAILABLE)) {
if (!self::getApp()->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
return;
}
@ -88,7 +88,7 @@ class Config extends BaseObject
public static function get($family, $key, $default_value = null, $refresh = false)
{
// Database isn't ready or populated yet, fallback to file config
if (!(self::getApp()->mode & App::MODE_DBCONFIGAVAILABLE)) {
if (!self::getApp()->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
return self::getApp()->getConfigValue($family, $key, $default_value);
}
@ -116,7 +116,7 @@ class Config extends BaseObject
public static function set($family, $key, $value)
{
// Database isn't ready or populated yet
if (!(self::getApp()->mode & App::MODE_DBCONFIGAVAILABLE)) {
if (!self::getApp()->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
return false;
}
@ -141,7 +141,7 @@ class Config extends BaseObject
public static function delete($family, $key)
{
// Database isn't ready or populated yet
if (!(self::getApp()->mode & App::MODE_DBCONFIGAVAILABLE)) {
if (!self::getApp()->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
return false;
}

View file

@ -4,7 +4,7 @@ namespace Friendica\Core\Config;
/**
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
interface IConfigAdapter
{

View file

@ -11,7 +11,7 @@ require_once 'include/dba.php';
*
* Default Config Adapter. Provides the best performance for pages loading few configuration variables.
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class JITConfigAdapter extends BaseObject implements IConfigAdapter
{

View file

@ -11,7 +11,7 @@ require_once 'include/dba.php';
*
* Default PConfig Adapter. Provides the best performance for pages loading few configuration variables.
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class JITPConfigAdapter extends BaseObject implements IPConfigAdapter
{

View file

@ -13,7 +13,7 @@ require_once 'include/dba.php';
*
* Minimizes the number of database queries to retrieve configuration values at the cost of memory.
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class PreloadConfigAdapter extends BaseObject implements IConfigAdapter
{

View file

@ -13,7 +13,7 @@ require_once 'include/dba.php';
*
* Minimizes the number of database queries to retrieve configuration values at the cost of memory.
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class PreloadPConfigAdapter extends BaseObject implements IPConfigAdapter
{

View file

@ -5,7 +5,7 @@ namespace Friendica\Core;
/**
* Description of Console
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class Console extends \Asika\SimpleConsole\Console
{
@ -14,6 +14,7 @@ class Console extends \Asika\SimpleConsole\Console
protected $customHelpOptions = ['h', 'help', '?'];
protected $subConsoles = [
'cache' => __NAMESPACE__ . '\Console\Cache',
'config' => __NAMESPACE__ . '\Console\Config',
'createdoxygen' => __NAMESPACE__ . '\Console\CreateDoxygen',
'docbloxerrorchecker' => __NAMESPACE__ . '\Console\DocBloxErrorChecker',
@ -37,6 +38,7 @@ class Console extends \Asika\SimpleConsole\Console
Usage: bin/console [--version] [-h|--help|-?] <command> [<args>] [-v]
Commands:
cache Manage node cache
config Edit site config
createdoxygen Generate Doxygen headers
dbstructure Do database updates

View file

@ -39,7 +39,7 @@ HELP;
protected function doExecute()
{
$a = get_app();
$a = \Friendica\BaseObject::getApp();
if ($this->getOption('v')) {
$this->out('Class: ' . __CLASS__);
@ -56,7 +56,7 @@ HELP;
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
}
if ($a->mode === App::MODE_INSTALL) {
if ($a->getMode()->isInstall()) {
throw new RuntimeException('Friendica isn\'t properly installed yet.');
}

View file

@ -3,15 +3,14 @@
namespace Friendica\Core\Console;
use Asika\SimpleConsole\Console;
use Friendica\App;
use Friendica\BaseObject;
use Friendica\Core\Config;
use Friendica\Core\Install;
use Friendica\Core\Theme;
use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use RuntimeException;
require_once 'mod/install.php';
require_once 'include/dba.php';
class AutomaticInstallation extends Console
@ -30,20 +29,21 @@ Notes
Not checking .htaccess/URL-Rewrite during CLI installation.
Options
-h|--help|-? Show help information
-v Show more debug information.
-a All setup checks are required (except .htaccess)
-f|--file <config> prepared config file (e.g. "config/local.ini.php" itself) which will override every other config option - except the environment variables)
-s|--savedb Save the DB credentials to the file (if environment variables is used)
-H|--dbhost <host> The host of the mysql/mariadb database (env MYSQL_HOST)
-p|--dbport <port> The port of the mysql/mariadb database (env MYSQL_PORT)
-d|--dbdata <database> The name of the mysql/mariadb database (env MYSQL_DATABASE)
-U|--dbuser <username> The username of the mysql/mariadb database login (env MYSQL_USER or MYSQL_USERNAME)
-P|--dbpass <password> The password of the mysql/mariadb database login (env MYSQL_PASSWORD)
-b|--phppath <path> The path of the PHP binary (env FRIENDICA_PHP_PATH)
-A|--admin <mail> The admin email address of Friendica (env FRIENDICA_ADMIN_MAIL)
-T|--tz <timezone> The timezone of Friendica (env FRIENDICA_TZ)
-L|--lang <language> The language of Friendica (env FRIENDICA_LANG)
-h|--help|-? Show help information
-v Show more debug information.
-a All setup checks are required (except .htaccess)
-f|--file <config> prepared config file (e.g. "config/local.ini.php" itself) which will override every other config option - except the environment variables)
-s|--savedb Save the DB credentials to the file (if environment variables is used)
-H|--dbhost <host> The host of the mysql/mariadb database (env MYSQL_HOST)
-p|--dbport <port> The port of the mysql/mariadb database (env MYSQL_PORT)
-d|--dbdata <database> The name of the mysql/mariadb database (env MYSQL_DATABASE)
-U|--dbuser <username> The username of the mysql/mariadb database login (env MYSQL_USER or MYSQL_USERNAME)
-P|--dbpass <password> The password of the mysql/mariadb database login (env MYSQL_PASSWORD)
-u|--urlpath <url_path> The URL path of Friendica - f.e. '/friendica' (env FRIENDICA_URL_PATH)
-b|--phppath <php_path> The path of the PHP binary (env FRIENDICA_PHP_PATH)
-A|--admin <mail> The admin email address of Friendica (env FRIENDICA_ADMIN_MAIL)
-T|--tz <timezone> The timezone of Friendica (env FRIENDICA_TZ)
-L|--lang <language> The language of Friendica (env FRIENDICA_LANG)
Environment variables
MYSQL_HOST The host of the mysql/mariadb database (mandatory if mysql and environment is used)
@ -51,6 +51,7 @@ Environment variables
MYSQL_USERNAME|MYSQL_USER The username of the mysql/mariadb database login (MYSQL_USERNAME is for mysql, MYSQL_USER for mariadb)
MYSQL_PASSWORD The password of the mysql/mariadb database login
MYSQL_DATABASE The name of the mysql/mariadb database
FRIENDICA_URL_PATH The URL path of Friendica (f.e. '/friendica')
FRIENDICA_PHP_PATH The path of the PHP binary
FRIENDICA_ADMIN_MAIL The admin email address of Friendica (this email will be used for admin access)
FRIENDICA_TZ The timezone of Friendica
@ -75,6 +76,8 @@ HELP;
$a = BaseObject::getApp();
$install = new Install();
// if a config file is set,
$config_file = $this->getOption(['f', 'file']);
@ -82,8 +85,8 @@ HELP;
if ($config_file != 'config' . DIRECTORY_SEPARATOR . 'local.ini.php') {
// Copy config file
$this->out("Copying config file...\n");
if (!copy($a->basepath . DIRECTORY_SEPARATOR . $config_file, $a->basepath . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'local.ini.php')) {
throw new RuntimeException("ERROR: Saving config file failed. Please copy '$config_file' to '$a->basepath" . DIRECTORY_SEPARATOR . "config" . DIRECTORY_SEPARATOR . "local.ini.php' manually.\n");
if (!copy($a->getBasePath() . DIRECTORY_SEPARATOR . $config_file, $a->getBasePath() . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'local.ini.php')) {
throw new RuntimeException("ERROR: Saving config file failed. Please copy '$config_file' to '" . $a->getBasePath() . "'" . DIRECTORY_SEPARATOR . "config" . DIRECTORY_SEPARATOR . "local.ini.php' manually.\n");
}
}
@ -102,21 +105,23 @@ HELP;
$db_data = $this->getOption(['d', 'dbdata'], ($save_db) ? getenv('MYSQL_DATABASE') : '');
$db_user = $this->getOption(['U', 'dbuser'], ($save_db) ? getenv('MYSQL_USER') . getenv('MYSQL_USERNAME') : '');
$db_pass = $this->getOption(['P', 'dbpass'], ($save_db) ? getenv('MYSQL_PASSWORD') : '');
$php_path = $this->getOption(['b', 'phppath'], (!empty('FRIENDICA_PHP_PATH')) ? getenv('FRIENDICA_PHP_PATH') : '');
$url_path = $this->getOption(['u', 'urlpath'], (!empty('FRIENDICA_URL_PATH')) ? getenv('FRIENDICA_URL_PATH') : null);
$php_path = $this->getOption(['b', 'phppath'], (!empty('FRIENDICA_PHP_PATH')) ? getenv('FRIENDICA_PHP_PATH') : null);
$admin_mail = $this->getOption(['A', 'admin'], (!empty('FRIENDICA_ADMIN_MAIL')) ? getenv('FRIENDICA_ADMIN_MAIL') : '');
$tz = $this->getOption(['T', 'tz'], (!empty('FRIENDICA_TZ')) ? getenv('FRIENDICA_TZ') : '');
$lang = $this->getOption(['L', 'lang'], (!empty('FRIENDICA_LANG')) ? getenv('FRIENDICA_LANG') : '');
Install::createConfig(
$install->createConfig(
$php_path,
$url_path,
((!empty($db_port)) ? $db_host . ':' . $db_port : $db_host),
$db_user,
$db_pass,
$db_data,
$php_path,
$tz,
$lang,
$admin_mail
$admin_mail,
$a->getBasePath()
);
}
@ -126,7 +131,10 @@ HELP;
$this->out("Checking basic setup...\n");
$checkResults = [];
$checkResults['basic'] = $this->runBasicChecks($a);
$this->runBasicChecks($install);
$checkResults['basic'] = $install->getChecks();
$errorMessage = $this->extractErrors($checkResults['basic']);
if ($errorMessage !== '') {
@ -151,7 +159,7 @@ HELP;
// Install database
$this->out("Inserting data into database...\n");
$checkResults['data'] = Install::installDatabaseStructure();
$checkResults['data'] = DBStructure::update(false, true, true);
if ($checkResults['data'] !== '') {
throw new RuntimeException("ERROR: DB Database creation error. Is the DB empty?\n");
@ -174,28 +182,26 @@ HELP;
}
/**
* @param App $app
* @return array
* @param Install $install the Installer instance
*/
private function runBasicChecks($app)
private function runBasicChecks(Install $install)
{
$checks = [];
Install::checkFunctions($checks);
Install::checkImagick($checks);
Install::checkLocalIni($checks);
Install::checkSmarty3($checks);
Install::checkKeys($checks);
$install->resetChecks();
$install->checkFunctions();
$install->checkImagick();
$install->checkLocalIni();
$install->checkSmarty3();
$install->checkKeys();
if (!empty(Config::get('config', 'php_path'))) {
Install::checkPHP(Config::get('config', 'php_path'), $checks);
if (!$install->checkPHP(Config::get('config', 'php_path'), true)) {
throw new RuntimeException(" ERROR: The php_path is not valid in the config.\n");
}
} else {
throw new RuntimeException(" ERROR: The php_path is not set in the config.\n");
}
$this->out(" NOTICE: Not checking .htaccess/URL-Rewrite during CLI installation.\n");
return $checks;
}
/**
@ -203,6 +209,7 @@ HELP;
* @param $db_user
* @param $db_pass
* @param $db_data
*
* @return array
*/
private function runDatabaseCheck($db_host, $db_user, $db_pass, $db_data)

178
src/Core/Console/Cache.php Normal file
View file

@ -0,0 +1,178 @@
<?php
namespace Friendica\Core\Console;
use Asika\SimpleConsole\CommandArgsException;
use Friendica\App;
use Friendica\Core;
use RuntimeException;
/**
* @brief tool to access the cache from the CLI
*
* With this script you can access the cache of your node from the CLI.
* You can read current values stored in the cache and set new values
* in cache keys.
*
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class Cache extends \Asika\SimpleConsole\Console
{
protected $helpOptions = ['h', 'help', '?'];
protected function getHelp()
{
$help = <<<HELP
console cache - Manage node cache
Synopsis
bin/console cache list [-h|--help|-?] [-v]
bin/console cache get <key> [-h|--help|-?] [-v]
bin/console cache set <key> <value> [-h|--help|-?] [-v]
bin/console cache flush [-h|--help|-?] [-v]
bin/console cache clear [-h|--help|-?] [-v]
Description
bin/console cache list [<prefix>]
List all cache keys, optionally filtered by a prefix
bin/console cache get <key>
Shows the value of the provided cache key
bin/console cache set <key> <value> [<ttl>]
Sets the value of the provided cache key, optionally with the provided TTL (time to live) with a default of five minutes.
bin/console cache flush
Clears expired cache keys
bin/console cache clear
Clears all cache keys
Options
-h|--help|-? Show help information
-v Show more debug information.
HELP;
return $help;
}
protected function doExecute()
{
$a = \Friendica\BaseObject::getApp();
if ($this->getOption('v')) {
$this->out('Executable: ' . $this->executable);
$this->out('Class: ' . __CLASS__);
$this->out('Arguments: ' . var_export($this->args, true));
$this->out('Options: ' . var_export($this->options, true));
}
if ($a->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
$this->out('Database isn\'t ready or populated yet, database cache won\'t be available');
}
Core\Cache::init();
if ($this->getOption('v')) {
$this->out('Cache Driver Name: ' . Core\Cache::$driver_name);
$this->out('Cache Driver Class: ' . Core\Cache::$driver_class);
}
switch ($this->getArgument(0)) {
case 'list':
$this->executeList();
break;
case 'get':
$this->executeGet();
break;
case 'set':
$this->executeSet();
break;
case 'flush':
$this->executeFlush();
break;
case 'clear':
$this->executeClear();
break;
}
if (count($this->args) == 0) {
$this->out($this->getHelp());
return 0;
}
return 0;
}
private function executeList()
{
$prefix = $this->getArgument(1);
$keys = Core\Cache::getAllKeys($prefix);
if (empty($prefix)) {
$this->out('Listing all cache keys:');
} else {
$this->out('Listing all cache keys starting with "' . $prefix . '":');
}
$count = 0;
foreach ($keys as $key) {
$this->out($key);
$count++;
}
$this->out($count . ' keys found');
}
private function executeGet()
{
if (count($this->args) >= 2) {
$key = $this->getArgument(1);
$value = Core\Cache::get($key);
$this->out("{$key} => " . var_export($value, true));
} else {
throw new CommandArgsException('Too few arguments for get');
}
}
private function executeSet()
{
if (count($this->args) >= 3) {
$key = $this->getArgument(1);
$value = $this->getArgument(2);
$duration = intval($this->getArgument(3, Core\Cache::FIVE_MINUTES));
if (is_array(Core\Cache::get($key))) {
throw new RuntimeException("$key is an array and can't be set using this command.");
}
$result = Core\Cache::set($key, $value, $duration);
if ($result) {
$this->out("{$key} <= " . Core\Cache::get($key));
} else {
$this->out("Unable to set {$key}");
}
} else {
throw new CommandArgsException('Too few arguments for set');
}
}
private function executeFlush()
{
$result = Core\Cache::clear();
if ($result) {
$this->out('Cache successfully flushed');
} else {
$this->out('Unable to flush the cache');
}
}
private function executeClear()
{
$result = Core\Cache::clear(false);
if ($result) {
$this->out('Cache successfully cleared');
} else {
$this->out('Unable to flush the cache');
}
}
}

View file

@ -1,11 +1,5 @@
<?php
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
namespace Friendica\Core\Console;
use Asika\SimpleConsole\CommandArgsException;
@ -13,9 +7,6 @@ use Friendica\App;
use Friendica\Core;
use RuntimeException;
require_once 'include/dba.php';
require_once 'include/text.php';
/**
* @brief tool to access the system config from the CLI
*
@ -37,8 +28,8 @@ require_once 'include/text.php';
* set to the value of the last parameter. (e.g. "system loglevel 0" will
* disable logging)
*
* @author Tobias Diekershoff
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Tobias Diekershoff <tobias.diekershoff@gmx.net>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class Config extends \Asika\SimpleConsole\Console
{
@ -80,7 +71,7 @@ HELP;
protected function doExecute()
{
$a = get_app();
$a = \Friendica\BaseObject::getApp();
if ($this->getOption('v')) {
$this->out('Executable: ' . $this->executable);
@ -93,7 +84,7 @@ HELP;
throw new CommandArgsException('Too many arguments');
}
if (!($a->mode & App::MODE_DBCONFIGAVAILABLE)) {
if (!$a->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
$this->out('Database isn\'t ready or populated yet, showing file config only');
}
@ -152,7 +143,7 @@ HELP;
if (count($this->args) == 0) {
Core\Config::load();
if (Core\Config::get('system', 'config_adapter') == 'jit' && $a->mode & App::MODE_DBCONFIGAVAILABLE) {
if (Core\Config::get('system', 'config_adapter') == 'jit' && $a->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
$this->out('Warning: The JIT (Just In Time) Config adapter doesn\'t support loading the entire configuration, showing file config only');
}

View file

@ -5,7 +5,7 @@ namespace Friendica\Core\Console;
/**
* Description of CreateDoxygen
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class CreateDoxygen extends \Asika\SimpleConsole\Console
{

View file

@ -11,9 +11,9 @@ require_once 'boot.php';
require_once 'include/dba.php';
/**
* @brief Does database updates from the command line
* @brief Performs database updates from the command line
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class DatabaseStructure extends \Asika\SimpleConsole\Console
{
@ -22,7 +22,7 @@ class DatabaseStructure extends \Asika\SimpleConsole\Console
protected function getHelp()
{
$help = <<<HELP
console dbstructure - Does database updates
console dbstructure - Performs database updates
Usage
bin/console dbstructure <command> [-h|--help|-?] [-v]

View file

@ -22,7 +22,7 @@ namespace Friendica\Core\Console;
* This is done for all files, so, in the end removing one file leads to a working doc build.
*
* @author Alexander Kampmann
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class DocBloxErrorChecker extends \Asika\SimpleConsole\Console
{
@ -60,7 +60,7 @@ HELP;
}
//return from util folder to frindica base dir
$dir = get_app()->get_basepath();
$dir = get_app()->getBasePath();
//stack for dirs to search
$dirstack = [];

View file

@ -8,7 +8,7 @@ namespace Friendica\Core\Console;
*
* Outputs a PHP file with language strings used by Friendica
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class Extract extends \Asika\SimpleConsole\Console
{

View file

@ -13,8 +13,8 @@ use Friendica\Model\Contact;
*
* License: AGPLv3 or later, same as Friendica
*
* @author Tobias Diekershoff <mrpetovan@gmail.com>
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Tobias Diekershoff <tobias.diekershoff@gmx.net>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class GlobalCommunityBlock extends \Asika\SimpleConsole\Console
{
@ -56,7 +56,7 @@ HELP;
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
}
if ($a->isInstallMode()) {
if ($a->getMode()->isInstall()) {
throw new \RuntimeException('Database isn\'t ready or populated yet');
}

View file

@ -19,8 +19,8 @@ require_once 'include/text.php';
*
* License: AGPLv3 or later, same as Friendica
*
* @author Tobias Diekershoff
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Tobias Diekershoff <tobias.diekershoff@gmx.net>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class GlobalCommunitySilence extends \Asika\SimpleConsole\Console
{
@ -65,7 +65,7 @@ HELP;
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
}
if ($a->isInstallMode()) {
if ($a->getMode()->isInstall()) {
throw new RuntimeException('Database isn\'t ready or populated yet');
}

View file

@ -10,7 +10,7 @@ require_once 'include/dba.php';
/**
* @brief Sets maintenance mode for this node
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class Maintenance extends \Asika\SimpleConsole\Console
{
@ -47,7 +47,7 @@ HELP;
protected function doExecute()
{
$a = get_app();
$a = \Friendica\BaseObject::getApp();
if ($this->getOption('v')) {
$this->out('Class: ' . __CLASS__);
@ -64,7 +64,7 @@ HELP;
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
}
if ($a->isInstallMode()) {
if ($a->getMode()->isInstall()) {
throw new \RuntimeException('Database isn\'t ready or populated yet');
}

View file

@ -57,7 +57,7 @@ HELP;
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
}
if ($a->isInstallMode()) {
if ($a->getMode()->isInstall()) {
throw new RuntimeException('Database isn\'t ready or populated yet');
}

View file

@ -5,7 +5,7 @@ namespace Friendica\Core\Console;
/**
* Read a strings.php file and create messages.po in the same directory
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class PhpToPo extends \Asika\SimpleConsole\Console
{

View file

@ -5,7 +5,7 @@ namespace Friendica\Core\Console;
/**
* Read a messages.po file and create strings.php in the same directory
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class PoToPhp extends \Asika\SimpleConsole\Console
{

View file

@ -3,29 +3,54 @@
namespace Friendica\Core\Console;
use Friendica\Core\L10n;
use Friendica\Core\Config;
/**
* @brief tool to block an account from the node
*
* With this tool, you can block an account in such a way, that no postings
* or comments this account writes are accepted to the node.
* Performs database post updates
*
* License: AGPLv3 or later, same as Friendica
*
* @author Tobias Diekershoff <mrpetovan@gmail.com>
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Tobias Diekershoff <tobias.diekershoff@gmx.net>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class PostUpdate extends \Asika\SimpleConsole\Console
{
protected $helpOptions = ['h', 'help', '?'];
protected function getHelp()
{
$help = <<<HELP
console postupdate - Performs database post updates
Usage
bin/console postupdate [-h|--help|-?] [--reset <version>]
Options
-h|--help|-? Show help information
--reset <version> Reset the post update version
HELP;
return $help;
}
protected function doExecute()
{
$a = get_app();
$a = \Friendica\BaseObject::getApp();
if (count($this->args) > 0) {
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
if ($this->getOption($this->helpOptions)) {
$this->out($this->getHelp());
return 0;
}
if ($a->isInstallMode()) {
$reset_version = $this->getOption('reset');
if (is_bool($reset_version)) {
$this->out($this->getHelp());
return 0;
} elseif ($reset_version) {
Config::set('system', 'post_update_version', $reset_version);
echo L10n::t('Post update version number has been set to %s.', $reset_version) . "\n";
return 0;
}
if ($a->getMode()->isInstall()) {
throw new \RuntimeException('Database isn\'t ready or populated yet');
}

View file

@ -6,7 +6,7 @@ namespace Friendica\Core\Console;
* Tired of chasing typos and finding them after a commit.
* Run this and quickly see if we've got any parse errors in our application files.
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class Typo extends \Asika\SimpleConsole\Console
{

View file

@ -6,52 +6,90 @@ namespace Friendica\Core;
use DOMDocument;
use Exception;
use Friendica\BaseObject;
use Friendica\Database\DBStructure;
use Friendica\Object\Image;
use Friendica\Util\Network;
/**
* Contains methods for installation purpose of Friendica
*/
class Install extends BaseObject
class Install
{
/**
* @var array the check outcomes
*/
private $checks;
/**
* Returns all checks made
*
* @return array the checks
*/
public function getChecks()
{
return $this->checks;
}
/**
* Resets all checks
*/
public function resetChecks()
{
$this->checks = [];
}
/**
* Install constructor.
*
*/
public function __construct()
{
$this->checks = [];
}
/**
* Checks the current installation environment. There are optional and mandatory checks.
*
* @param string $phpath Optional path to the PHP binary (Default is 'php')
* @param string $basepath The basepath of Friendica
* @param string $baseurl The baseurl of Friendica
* @param string $phpath Optional path to the PHP binary
*
* @return array First element is a list of all checks and their results,
* the second element is a list of passed checks
* @return bool if the check succeed
*/
public static function check($phpath = 'php')
public function checkAll($basepath, $baseurl, $phpath = null)
{
$checks = [];
$returnVal = true;
self::checkFunctions($checks);
if (isset($phpath)) {
if (!$this->checkPHP($phpath)) {
$returnVal = false;
}
}
self::checkImagick($checks);
if (!$this->checkFunctions()) {
$returnVal = false;
}
self::checkLocalIni($checks);
if (!$this->checkImagick()) {
$returnVal = false;
}
self::checkSmarty3($checks);
if (!$this->checkLocalIni()) {
$returnVal = false;
}
self::checkKeys($checks);
if (!$this->checkSmarty3()) {
$returnVal = false;
}
self::checkPHP($phpath, $checks);
if (!$this->checkKeys()) {
$returnVal = false;
}
self::checkHtAccess($checks);
if (!$this->checkHtAccess($basepath, $baseurl)) {
$returnVal = false;
}
$checkspassed = array_reduce($checks,
function ($v, $c) {
if (!empty($c['require'])) {
$v = $v && $c['status'];
}
return $v;
},
true);
return array($checks, $checkspassed);
return $returnVal;
}
/**
@ -64,15 +102,19 @@ class Install extends BaseObject
* @param string $dbuser Username of the Database connection credentials
* @param string $dbpass Password of the Database connection credentials
* @param string $dbdata Name of the Database
* @param string $phpath Path to the PHP-Binary (e.g. 'php' or '/usr/bin/php')
* @param string $timezone Timezone of the Friendica Installaton (e.g. 'Europe/Berlin')
* @param string $language 2-letter ISO 639-1 code (eg. 'en')
* @param string $adminmail Mail-Adress of the administrator
* @param string $basepath The basepath of Friendica
* @param string $phpath Path to the PHP-Binary (optional, if not set e.g. 'php' or '/usr/bin/php')
*
* @return bool|string true if the config was created, the text if something went wrong
*/
public static function createConfig($urlpath, $dbhost, $dbuser, $dbpass, $dbdata, $phpath, $timezone, $language, $adminmail)
public function createConfig($phppath, $urlpath, $dbhost, $dbuser, $dbpass, $dbdata, $timezone, $language, $adminmail, $basepath)
{
$tpl = get_markup_template('local.ini.tpl');
$txt = replace_macros($tpl,[
'$phpath' => $phppath,
'$dbhost' => $dbhost,
'$dbuser' => $dbuser,
'$dbpass' => $dbpass,
@ -80,37 +122,36 @@ class Install extends BaseObject
'$timezone' => $timezone,
'$language' => $language,
'$urlpath' => $urlpath,
'$phpath' => $phpath,
'$adminmail' => $adminmail,
]);
$app = self::getApp();
$result = file_put_contents($basepath . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'local.ini.php', $txt);
$result = file_put_contents($app->basepath . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'local.ini.php', $txt);
if (!$result) {
$app->data['txt'] = $txt;
return $txt;
} else {
return true;
}
}
/**
* Adds new checks to the array $checks
*
* @param array $checks The list of all checks (by-ref parameter!)
* @param string $title The title of the current check
* @param bool $status 1 = check passed, 0 = check not passed
* @param bool $required 1 = check is mandatory, 0 = check is optional
* @param string $help A help-string for the current check
* @param string $error_msg Optional. A error message, if the current check failed
*/
private static function addCheck(&$checks, $title, $status, $required, $help, $error_msg = "")
private function addCheck($title, $status, $required, $help, $error_msg = "")
{
$checks[] = [
array_push($this->checks, [
'title' => $title,
'status' => $status,
'required' => $required,
'help' => $help,
'error_msg' => $error_msg,
];
]);
}
/**
@ -122,18 +163,21 @@ class Install extends BaseObject
* - Checks if it is the CLI version
* - Checks if "register_argc_argv" is enabled
*
* @param string $phpath Optional. The Path to the PHP-Binary
* @param array $checks The list of all checks (by-ref parameter!)
* @param string $phppath Optional. The Path to the PHP-Binary
* @param bool $required Optional. If set to true, the PHP-Binary has to exist (Default false)
*
* @return bool false if something required failed
*/
public static function checkPHP($phpath, &$checks)
public function checkPHP($phppath = null, $required = false)
{
$passed = $passed2 = $passed3 = false;
if (strlen($phpath)) {
$passed = file_exists($phpath);
if (isset($phppath)) {
$passed = file_exists($phppath);
} else {
$phpath = trim(shell_exec('which php'));
$passed = strlen($phpath);
$phppath = trim(shell_exec('which php'));
$passed = strlen($phppath);
}
$help = "";
if (!$passed) {
$help .= L10n::t('Could not find a command line version of PHP in the web server PATH.') . EOL;
@ -141,15 +185,15 @@ class Install extends BaseObject
$help .= EOL . EOL;
$tpl = get_markup_template('field_input.tpl');
$help .= replace_macros($tpl, [
'$field' => ['phpath', L10n::t('PHP executable path'), $phpath, L10n::t('Enter full path to php executable. You can leave this blank to continue the installation.')],
'$field' => ['phpath', L10n::t('PHP executable path'), $phppath, L10n::t('Enter full path to php executable. You can leave this blank to continue the installation.')],
]);
$phpath = "";
$phppath = "";
}
self::addCheck($checks, L10n::t('Command line PHP').($passed?" (<tt>$phpath</tt>)":""), $passed, false, $help);
$this->addCheck(L10n::t('Command line PHP') . ($passed ? " (<tt>$phppath</tt>)" : ""), $passed, false, $help);
if ($passed) {
$cmd = "$phpath -v";
$cmd = "$phppath -v";
$result = trim(shell_exec($cmd));
$passed2 = (strpos($result, "(cli)") !== false);
list($result) = explode("\n", $result);
@ -158,21 +202,30 @@ class Install extends BaseObject
$help .= L10n::t("PHP executable is not the php cli binary \x28could be cgi-fgci version\x29") . EOL;
$help .= L10n::t('Found PHP version: ') . "<tt>$result</tt>";
}
self::addCheck($checks, L10n::t('PHP cli binary'), $passed2, true, $help);
$this->addCheck(L10n::t('PHP cli binary'), $passed2, true, $help);
} else {
// return if it was required
return $required;
}
if ($passed2) {
$str = autoname(8);
$cmd = "$phpath testargs.php $str";
$cmd = "$phppath testargs.php $str";
$result = trim(shell_exec($cmd));
$passed3 = $result == $str;
$help = "";
if (!$passed3) {
$help .= L10n::t('The command line version of PHP on your system does not have "register_argc_argv" enabled.') . EOL;
$help .= L10n::t('This is required for message delivery to work.');
} else {
$this->phppath = $phppath;
}
self::addCheck($checks, L10n::t('PHP register_argc_argv'), $passed3, true, $help);
$this->addCheck(L10n::t('PHP register_argc_argv'), $passed3, true, $help);
}
// passed2 & passed3 are required if first check passed
return $passed2 && $passed3;
}
/**
@ -182,12 +235,13 @@ class Install extends BaseObject
*
* - Checks, if the command "openssl_pkey_new" is available
*
* @param array $checks The list of all checks (by-ref parameter!)
* @return bool false if something required failed
*/
public static function checkKeys(&$checks)
public function checkKeys()
{
$help = '';
$res = false;
$status = true;
if (function_exists('openssl_pkey_new')) {
$res = openssl_pkey_new([
@ -201,8 +255,34 @@ class Install extends BaseObject
if (!$res) {
$help .= L10n::t('Error: the "openssl_pkey_new" function on this system is not able to generate encryption keys') . EOL;
$help .= L10n::t('If running under Windows, please see "http://www.php.net/manual/en/openssl.installation.php".');
$status = false;
}
self::addCheck($checks, L10n::t('Generate encryption keys'), $res, true, $help);
$this->addCheck(L10n::t('Generate encryption keys'), $res, true, $help);
return $status;
}
/**
* PHP basic function check
*
* @param string $name The name of the function
* @param string $title The (localized) title of the function
* @param string $help The (localized) help of the function
* @param boolean $required If true, this check is required
*
* @return bool false, if the check failed
*/
private function checkFunction($name, $title, $help, $required)
{
$currHelp = '';
$status = true;
if (!function_exists($name)) {
$currHelp = $help;
$status = false;
}
$this->addCheck($title, $status, $required, $currHelp);
return $status || (!$status && !$required);
}
/**
@ -218,70 +298,93 @@ class Install extends BaseObject
* - iconv
* - POSIX
*
* @param array $checks The list of all checks (by-ref parameter!)
* @return bool false if something required failed
*/
public static function checkFunctions(&$checks)
public function checkFunctions()
{
$ck_funcs = [];
self::addCheck($ck_funcs, L10n::t('libCurl PHP module'), true, true, "");
self::addCheck($ck_funcs, L10n::t('GD graphics PHP module'), true, true, "");
self::addCheck($ck_funcs, L10n::t('OpenSSL PHP module'), true, true, "");
self::addCheck($ck_funcs, L10n::t('PDO or MySQLi PHP module'), true, true, "");
self::addCheck($ck_funcs, L10n::t('mb_string PHP module'), true, true, "");
self::addCheck($ck_funcs, L10n::t('XML PHP module'), true, true, "");
self::addCheck($ck_funcs, L10n::t('iconv PHP module'), true, true, "");
self::addCheck($ck_funcs, L10n::t('POSIX PHP module'), true, true, "");
$returnVal = true;
$help = '';
$status = true;
if (function_exists('apache_get_modules')) {
if (! in_array('mod_rewrite',apache_get_modules())) {
self::addCheck($ck_funcs, L10n::t('Apache mod_rewrite module'), false, true, L10n::t('Error: Apache webserver mod-rewrite module is required but not installed.'));
} else {
self::addCheck($ck_funcs, L10n::t('Apache mod_rewrite module'), true, true, "");
if (!in_array('mod_rewrite', apache_get_modules())) {
$help = L10n::t('Error: Apache webserver mod-rewrite module is required but not installed.');
$status = false;
$returnVal = false;
}
}
$this->addCheck(L10n::t('Apache mod_rewrite module'), $status, true, $help);
if (!function_exists('curl_init')) {
$ck_funcs[0]['status'] = false;
$ck_funcs[0]['help'] = L10n::t('Error: libCURL PHP module required but not installed.');
}
if (!function_exists('imagecreatefromjpeg')) {
$ck_funcs[1]['status'] = false;
$ck_funcs[1]['help'] = L10n::t('Error: GD graphics PHP module with JPEG support required but not installed.');
}
if (!function_exists('openssl_public_encrypt')) {
$ck_funcs[2]['status'] = false;
$ck_funcs[2]['help'] = L10n::t('Error: openssl PHP module required but not installed.');
}
$help = '';
$status = true;
if (!function_exists('mysqli_connect') && !class_exists('pdo')) {
$ck_funcs[3]['status'] = false;
$ck_funcs[3]['help'] = L10n::t('Error: PDO or MySQLi PHP module required but not installed.');
$status = false;
$help = L10n::t('Error: PDO or MySQLi PHP module required but not installed.');
$returnVal = false;
} else {
if (!function_exists('mysqli_connect') && class_exists('pdo') && !in_array('mysql', \PDO::getAvailableDrivers())) {
$status = false;
$help = L10n::t('Error: The MySQL driver for PDO is not installed.');
$returnVal = false;
}
}
if (!function_exists('mysqli_connect') && class_exists('pdo') && !in_array('mysql', \PDO::getAvailableDrivers())) {
$ck_funcs[3]['status'] = false;
$ck_funcs[3]['help'] = L10n::t('Error: The MySQL driver for PDO is not installed.');
}
if (!function_exists('mb_strlen')) {
$ck_funcs[4]['status'] = false;
$ck_funcs[4]['help'] = L10n::t('Error: mb_string PHP module required but not installed.');
}
if (!function_exists('iconv_strlen')) {
$ck_funcs[6]['status'] = false;
$ck_funcs[6]['help'] = L10n::t('Error: iconv PHP module required but not installed.');
}
if (!function_exists('posix_kill')) {
$ck_funcs[7]['status'] = false;
$ck_funcs[7]['help'] = L10n::t('Error: POSIX PHP module required but not installed.');
}
$checks = array_merge($checks, $ck_funcs);
$this->addCheck(L10n::t('PDO or MySQLi PHP module'), $status, true, $help);
// check for XML DOM Documents being able to be generated
$help = '';
$status = true;
try {
$xml = new DOMDocument();
} catch (Exception $e) {
$ck_funcs[5]['status'] = false;
$ck_funcs[5]['help'] = L10n::t('Error, XML PHP module required but not installed.');
$help = L10n::t('Error, XML PHP module required but not installed.');
$status = false;
$returnVal = false;
}
$this->addCheck(L10n::t('XML PHP module'), $status, true, $help);
$status = $this->checkFunction('curl_init',
L10n::t('libCurl PHP module'),
L10n::t('Error: libCURL PHP module required but not installed.'),
true
);
$returnVal = $returnVal ? $status : false;
$status = $this->checkFunction('imagecreatefromjpeg',
L10n::t('GD graphics PHP module'),
L10n::t('Error: GD graphics PHP module with JPEG support required but not installed.'),
true
);
$returnVal = $returnVal ? $status : false;
$status = $this->checkFunction('openssl_public_encrypt',
L10n::t('OpenSSL PHP module'),
L10n::t('Error: openssl PHP module required but not installed.'),
true
);
$returnVal = $returnVal ? $status : false;
$status = $this->checkFunction('mb_strlen',
L10n::t('mb_string PHP module'),
L10n::t('Error: mb_string PHP module required but not installed.'),
true
);
$returnVal = $returnVal ? $status : false;
$status = $this->checkFunction('iconv_strlen',
L10n::t('iconv PHP module'),
L10n::t('Error: iconv PHP module required but not installed.'),
true
);
$returnVal = $returnVal ? $status : false;
$status = $this->checkFunction('posix_kill',
L10n::t('POSIX PHP module'),
L10n::t('Error: POSIX PHP module required but not installed.'),
true
);
$returnVal = $returnVal ? $status : false;
return $returnVal;
}
/**
@ -289,9 +392,9 @@ class Install extends BaseObject
*
* Checks if it's possible to create the "config/local.ini.php"
*
* @param array $checks The list of all checks (by-ref parameter!)
* @return bool false if something required failed
*/
public static function checkLocalIni(&$checks)
public function checkLocalIni()
{
$status = true;
$help = "";
@ -305,8 +408,10 @@ class Install extends BaseObject
$help .= L10n::t('You can alternatively skip this procedure and perform a manual installation. Please see the file "INSTALL.txt" for instructions.') . EOL;
}
self::addCheck($checks, L10n::t('config/local.ini.php is writable'), $status, false, $help);
$this->addCheck(L10n::t('config/local.ini.php is writable'), $status, false, $help);
// Local INI File is not required
return true;
}
/**
@ -314,9 +419,9 @@ class Install extends BaseObject
*
* Checks, if the directory of Smarty3 is writable
*
* @param array $checks The list of all checks (by-ref parameter!)
* @return bool false if something required failed
*/
public static function checkSmarty3(&$checks)
public function checkSmarty3()
{
$status = true;
$help = "";
@ -329,7 +434,9 @@ class Install extends BaseObject
$help .= L10n::t("Note: as a security measure, you should give the web server write access to view/smarty3/ only--not the template files \x28.tpl\x29 that it contains.") . EOL;
}
self::addCheck($checks, L10n::t('view/smarty3 is writable'), $status, true, $help);
$this->addCheck(L10n::t('view/smarty3 is writable'), $status, true, $help);
return $status;
}
/**
@ -337,34 +444,39 @@ class Install extends BaseObject
*
* Checks, if "url_rewrite" is enabled in the ".htaccess" file
*
* @param array $checks The list of all checks (by-ref parameter!)
* @param string $basepath The basepath of the app
* @param string $baseurl The baseurl of the app
* @return bool false if something required failed
*/
public static function checkHtAccess(&$checks)
public function checkHtAccess($basepath, $baseurl)
{
$status = true;
$help = "";
$error_msg = "";
if (function_exists('curl_init')) {
$test = Network::fetchUrlFull(System::baseUrl() . "/install/testrewrite");
$fetchResult = Network::fetchUrlFull($basepath . "/install/testrewrite");
$url = normalise_link(System::baseUrl() . "/install/testrewrite");
if ($test['body'] != "ok") {
$test = Network::fetchUrlFull($url);
$url = normalise_link($baseurl . "/install/testrewrite");
if ($fetchResult->getBody() != "ok") {
$fetchResult = Network::fetchUrlFull($url);
}
if ($test['body'] != "ok") {
if ($fetchResult->getBody() != "ok") {
$status = false;
$help = L10n::t('Url rewrite in .htaccess is not working. Check your server configuration.');
$error_msg = [];
$error_msg['head'] = L10n::t('Error message from Curl when fetching');
$error_msg['url'] = $test['redirect_url'];
$error_msg['msg'] = defaults($test, 'error', '');
$error_msg['url'] = $fetchResult->getRedirectUrl();
$error_msg['msg'] = $fetchResult->getError();
}
self::addCheck($checks, L10n::t('Url rewrite is working'), $status, true, $help, $error_msg);
$this->addCheck(L10n::t('Url rewrite is working'), $status, true, $help, $error_msg);
} else {
// cannot check modrewrite if libcurl is not installed
/// @TODO Maybe issue warning here?
}
return $status;
}
/**
@ -372,9 +484,9 @@ class Install extends BaseObject
*
* Checks, if the imagick module is available
*
* @param array $checks The list of all checks (by-ref parameter!)
* @return bool false if something required failed
*/
public static function checkImagick(&$checks)
public function checkImagick()
{
$imagick = false;
$gif = false;
@ -386,25 +498,16 @@ class Install extends BaseObject
$gif = true;
}
}
if ($imagick == false) {
self::addCheck($checks, L10n::t('ImageMagick PHP extension is not installed'), $imagick, false, "");
if (!$imagick) {
$this->addCheck(L10n::t('ImageMagick PHP extension is not installed'), $imagick, false, "");
} else {
self::addCheck($checks, L10n::t('ImageMagick PHP extension is installed'), $imagick, false, "");
$this->addCheck(L10n::t('ImageMagick PHP extension is installed'), $imagick, false, "");
if ($imagick) {
self::addCheck($checks, L10n::t('ImageMagick supports GIF'), $gif, false, "");
$this->addCheck(L10n::t('ImageMagick supports GIF'), $gif, false, "");
}
}
}
/**
* Installs the Database structure
*
* @return string A possible error
*/
public static function installDatabaseStructure()
{
$errors = DBStructure::update(false, true, true);
return $errors;
// Imagick is not required
return true;
}
}

View file

@ -183,6 +183,10 @@ class L10n extends BaseObject
{
$a = self::getApp();
if (!is_numeric($count)) {
logger('Non numeric count called by ' . System::callstack(20));
}
$lang = Config::get('system', 'language');
if (!empty($a->strings[$singular])) {

View file

@ -631,7 +631,7 @@ class NotificationsManager extends BaseObject
// We have to distinguish between these two because they use different data.
// Contact suggestions
if ($it['fid']) {
$return_addr = bin2hex(self::getApp()->user['nickname'] . '@' . self::getApp()->get_hostname() . ((self::getApp()->urlpath) ? '/' . self::getApp()->urlpath : ''));
$return_addr = bin2hex(self::getApp()->user['nickname'] . '@' . self::getApp()->getHostName() . ((self::getApp()->getURLPath()) ? '/' . self::getApp()->getURLPath() : ''));
$intro = [
'label' => 'friend_suggestion',

View file

@ -29,12 +29,14 @@ class PConfig extends BaseObject
public static function init($uid)
{
$a = self::getApp();
// Database isn't ready or populated yet
if (!(self::getApp()->mode & App::MODE_DBCONFIGAVAILABLE)) {
if (!$a->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
return;
}
if (self::getApp()->getConfigValue('system', 'config_adapter') == 'preload') {
if ($a->getConfigValue('system', 'config_adapter') == 'preload') {
self::$adapter = new Config\PreloadPConfigAdapter($uid);
} else {
self::$adapter = new Config\JITPConfigAdapter($uid);
@ -55,7 +57,7 @@ class PConfig extends BaseObject
public static function load($uid, $family)
{
// Database isn't ready or populated yet
if (!(self::getApp()->mode & App::MODE_DBCONFIGAVAILABLE)) {
if (!self::getApp()->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
return;
}
@ -84,7 +86,7 @@ class PConfig extends BaseObject
public static function get($uid, $family, $key, $default_value = null, $refresh = false)
{
// Database isn't ready or populated yet
if (!(self::getApp()->mode & App::MODE_DBCONFIGAVAILABLE)) {
if (!self::getApp()->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
return;
}
@ -113,7 +115,7 @@ class PConfig extends BaseObject
public static function set($uid, $family, $key, $value)
{
// Database isn't ready or populated yet
if (!(self::getApp()->mode & App::MODE_DBCONFIGAVAILABLE)) {
if (!self::getApp()->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
return false;
}
@ -139,7 +141,7 @@ class PConfig extends BaseObject
public static function delete($uid, $family, $key)
{
// Database isn't ready or populated yet
if (!(self::getApp()->mode & App::MODE_DBCONFIGAVAILABLE)) {
if (!self::getApp()->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
return false;
}

View file

@ -9,31 +9,37 @@ use Friendica\Util\Network;
/**
* Manage compatibility with federated networks
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class Protocol
{
const DFRN = 'dfrn'; // Friendica, Mistpark, other DFRN implementations
const DIASPORA = 'dspr'; // Diaspora
const DIASPORA2 = 'dspc'; // Diaspora connector
const STATUSNET = 'stac'; // Statusnet connector
const OSTATUS = 'stat'; // GNU-social, Pleroma, Mastodon, other OStatus implementations
const FEED = 'feed'; // RSS/Atom feeds with no known "post/notify" protocol
const MAIL = 'mail'; // IMAP/POP
const XMPP = 'xmpp'; // XMPP - Currently unsupported
// Native support
const ACTIVITYPUB = 'apub'; // ActivityPub
const DFRN = 'dfrn'; // Friendica, Mistpark, other DFRN implementations
const DIASPORA = 'dspr'; // Diaspora
const FEED = 'feed'; // RSS/Atom feeds with no known "post/notify" protocol
const MAIL = 'mail'; // IMAP/POP
const OSTATUS = 'stat'; // GNU-social, Pleroma, Mastodon, other OStatus implementations
const FACEBOOK = 'face'; // Facebook API
const LINKEDIN = 'lnkd'; // LinkedIn
const MYSPACE = 'mysp'; // MySpace - Currently unsupported
const GPLUS = 'goog'; // Google+
const PUMPIO = 'pump'; // pump.io
const TWITTER = 'twit'; // Twitter
const NATIVE_SUPPORT = [self::DFRN, self::DIASPORA, self::OSTATUS, self::FEED, self::MAIL, self::ACTIVITYPUB];
// Supported through a connector
const APPNET = 'apdn'; // app.net - Dead protocol
const DIASPORA2 = 'dspc'; // Diaspora connector
const FACEBOOK = 'face'; // Facebook API
const GPLUS = 'goog'; // Google+
const LINKEDIN = 'lnkd'; // LinkedIn
const PUMPIO = 'pump'; // pump.io
const STATUSNET = 'stac'; // Statusnet connector
const TWITTER = 'twit'; // Twitter
const NEWS = 'nntp'; // Network News Transfer Protocol - Currently unsupported
const ICALENDAR = 'ical'; // iCalendar - Currently unsupported
const PNUT = 'pnut'; // pnut.io - Currently unsupported
const ZOT = 'zot!'; // Zot! - Currently unsupported
// Currently unsupported
const ICALENDAR = 'ical'; // iCalendar
const MYSPACE = 'mysp'; // MySpace
const NEWS = 'nntp'; // Network News Transfer Protocol
const PNUT = 'pnut'; // pnut.io
const XMPP = 'xmpp'; // XMPP
const ZOT = 'zot!'; // Zot!
const PHANTOM = 'unkn'; // Place holder
@ -102,6 +108,13 @@ class Protocol
}
}
// Mastodon, Pleroma
if (preg_match('=https?://(.+?)/users/(.+)=ism', $profile_url, $matches)
|| preg_match('=https?://(.+?)/@(.+)=ism', $profile_url, $matches)
) {
return self::ACTIVITYPUB;
}
// pumpio (http://host.name/user)
if (preg_match('=https?://([\.\w]+)/([\.\w]+)$=ism', $profile_url, $matches)) {
return self::PUMPIO;

View file

@ -11,7 +11,7 @@ use Friendica\Core\Session\DatabaseSessionHandler;
/**
* High-level Session service class
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class Session
{

View file

@ -13,7 +13,7 @@ require_once 'include/text.php';
/**
* SessionHandler using Friendica Cache
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class CacheSessionHandler extends BaseObject implements SessionHandlerInterface
{

View file

@ -14,7 +14,7 @@ require_once 'include/text.php';
/**
* SessionHandler using database
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class DatabaseSessionHandler extends BaseObject implements SessionHandlerInterface
{

View file

@ -27,7 +27,7 @@ class System extends BaseObject
*/
public static function baseUrl($ssl = false)
{
return self::getApp()->get_baseurl($ssl);
return self::getApp()->getBaseURL($ssl);
}
/**
@ -39,7 +39,7 @@ class System extends BaseObject
*/
public static function removedBaseUrl($orig_url)
{
return self::getApp()->remove_baseurl($orig_url);
return self::getApp()->removeBaseURL($orig_url);
}
/**
@ -161,6 +161,18 @@ class System extends BaseObject
killme();
}
/**
* Generates a random string in the UUID format
*
* @param bool|string $prefix A given prefix (default is empty)
* @return string a generated UUID
*/
public static function createUUID($prefix = '')
{
$guid = System::createGUID(32, $prefix);
return substr($guid, 0, 8). '-' . substr($guid, 8, 4) . '-' . substr($guid, 12, 4) . '-' . substr($guid, 16, 4) . '-' . substr($guid, 20, 12);
}
/**
* Generates a GUID with the given parameters
*
@ -173,7 +185,7 @@ class System extends BaseObject
if (is_bool($prefix) && !$prefix) {
$prefix = '';
} elseif (empty($prefix)) {
$prefix = hash('crc32', self::getApp()->get_hostname());
$prefix = hash('crc32', self::getApp()->getHostName());
}
while (strlen($prefix) < ($size - 13)) {
@ -204,6 +216,26 @@ class System extends BaseObject
return substr($trailer . uniqid('') . mt_rand(), 0, 26);
}
/**
* Returns the current Load of the System
*
* @return integer
*/
public static function currentLoad()
{
if (!function_exists('sys_getloadavg')) {
return false;
}
$load_arr = sys_getloadavg();
if (!is_array($load_arr)) {
return false;
}
return max($load_arr[0], $load_arr[1]);
}
/// @todo Move the following functions from boot.php
/*
function killme()
@ -220,6 +252,5 @@ class System extends BaseObject
function get_cachefile($file, $writemode = true)
function get_itemcachepath()
function get_spoolpath()
function current_load()
*/
}

View file

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

View file

@ -8,6 +8,7 @@ use Friendica\Database\DBA;
use Friendica\Model\Process;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Network;
use Friendica\BaseObject;
require_once 'include/dba.php';
@ -62,7 +63,7 @@ class Worker
}
// Do we have too few memory?
if ($a->min_memory_reached()) {
if ($a->isMinMemoryReached()) {
logger('Pre check: Memory limit reached, quitting.', LOGGER_DEBUG);
return;
}
@ -117,12 +118,14 @@ class Worker
// Count active workers and compare them with a maximum value that depends on the load
if (self::tooMuchWorkers()) {
logger('Active worker limit reached, quitting.', LOGGER_DEBUG);
Lock::release('worker');
return;
}
// Check free memory
if ($a->min_memory_reached()) {
if ($a->isMinMemoryReached()) {
logger('Memory limit reached, quitting.', LOGGER_DEBUG);
Lock::release('worker');
return;
}
Lock::release('worker');
@ -150,7 +153,8 @@ class Worker
*/
private static function totalEntries()
{
return DBA::count('workerqueue', ["`executed` <= ? AND NOT `done`", NULL_DATE]);
return DBA::count('workerqueue', ["`executed` <= ? AND NOT `done` AND `next_try` < ?",
NULL_DATE, DateTimeFormat::utcNow()]);
}
/**
@ -160,7 +164,7 @@ class Worker
*/
private static function highestPriority()
{
$condition = ["`executed` <= ? AND NOT `done`", NULL_DATE];
$condition = ["`executed` <= ? AND NOT `done` AND `next_try` < ?", NULL_DATE, DateTimeFormat::utcNow()];
$workerqueue = DBA::selectFirst('workerqueue', ['priority'], $condition, ['order' => ['priority']]);
if (DBA::isResult($workerqueue)) {
return $workerqueue["priority"];
@ -178,7 +182,8 @@ class Worker
*/
private static function processWithPriorityActive($priority)
{
$condition = ["`priority` <= ? AND `executed` > ? AND NOT `done`", $priority, NULL_DATE];
$condition = ["`priority` <= ? AND `executed` > ? AND NOT `done` AND `next_try` < ?",
$priority, NULL_DATE, DateTimeFormat::utcNow()];
return DBA::exists('workerqueue', $condition);
}
@ -238,7 +243,7 @@ class Worker
self::execFunction($queue, $include, $argv, true);
$stamp = (float)microtime(true);
if (DBA::update('workerqueue', ['done' => true], ['id' => $queue["id"]])) {
if (DBA::update('workerqueue', ['done' => true], ['id' => $queue['id']])) {
Config::set('system', 'last_worker_execution', DateTimeFormat::utcNow());
}
self::$db_duration = (microtime(true) - $stamp);
@ -616,7 +621,7 @@ class Worker
$active = self::activeWorkers();
// Decrease the number of workers at higher load
$load = current_load();
$load = System::currentLoad();
if ($load) {
$maxsysload = intval(Config::get("system", "maxloadavg", 50));
@ -803,7 +808,8 @@ class Worker
$result = DBA::select(
'workerqueue',
['id'],
["`executed` <= ? AND `priority` < ? AND NOT `done`", NULL_DATE, $highest_priority],
["`executed` <= ? AND `priority` < ? AND NOT `done` AND `next_try` < ?",
NULL_DATE, $highest_priority, DateTimeFormat::utcNow()],
['limit' => $limit, 'order' => ['priority', 'created']]
);
@ -819,7 +825,8 @@ class Worker
$result = DBA::select(
'workerqueue',
['id'],
["`executed` <= ? AND `priority` > ? AND NOT `done`", NULL_DATE, $highest_priority],
["`executed` <= ? AND `priority` > ? AND NOT `done` AND `next_try` < ?",
NULL_DATE, $highest_priority, DateTimeFormat::utcNow()],
['limit' => $limit, 'order' => ['priority', 'created']]
);
@ -838,7 +845,8 @@ class Worker
$result = DBA::select(
'workerqueue',
['id'],
["`executed` <= ? AND NOT `done`", NULL_DATE],
["`executed` <= ? AND NOT `done` AND `next_try` < ?",
NULL_DATE, DateTimeFormat::utcNow()],
['limit' => $limit, 'order' => ['priority', 'created']]
);
@ -1114,6 +1122,35 @@ class Worker
return true;
}
/**
* Defers the current worker entry
*/
public static function defer()
{
if (empty(BaseObject::getApp()->queue)) {
return;
}
$queue = BaseObject::getApp()->queue;
$retrial = $queue['retrial'];
$id = $queue['id'];
if ($retrial > 14) {
logger('Id ' . $id . ' had been tried 14 times, it will be deleted now.', LOGGER_DEBUG);
DBA::delete('workerqueue', ['id' => $id]);
}
// Calculate the delay until the next trial
$delay = (($retrial + 3) ** 4) + (rand(1, 30) * ($retrial + 1));
$next = DateTimeFormat::utc('now + ' . $delay . ' seconds');
logger('Defer execution ' . $retrial . ' of id ' . $id . ' to ' . $next, LOGGER_DEBUG);
$fields = ['retrial' => $retrial + 1, 'next_try' => $next, 'executed' => NULL_DATE, 'pid' => 0];
DBA::update('workerqueue', $fields, ['id' => $id]);
}
/**
* Log active processes into the "process" table
*

View file

@ -570,7 +570,7 @@ class DBA
self::$errorno = $errorno;
}
$a->save_timestamp($stamp1, 'database');
$a->saveTimestamp($stamp1, 'database');
if ($a->getConfigValue('system', 'db_log')) {
$stamp2 = microtime(true);
@ -641,7 +641,7 @@ class DBA
self::$errorno = $errorno;
}
$a->save_timestamp($stamp, "database_write");
$a->saveTimestamp($stamp, "database_write");
return $retval;
}
@ -809,7 +809,7 @@ class DBA
}
}
$a->save_timestamp($stamp1, 'database');
$a->saveTimestamp($stamp1, 'database');
return $columns;
}
@ -821,7 +821,7 @@ class DBA
* @param array $param parameter array
* @param bool $on_duplicate_update Do an update on a duplicate entry
*
* @return boolean was the insert successfull?
* @return boolean was the insert successful?
*/
public static function insert($table, $param, $on_duplicate_update = false) {
@ -1547,7 +1547,7 @@ class DBA
break;
}
$a->save_timestamp($stamp1, 'database');
$a->saveTimestamp($stamp1, 'database');
return $ret;
}

View file

@ -83,10 +83,11 @@ class DBStructure
$body = sprintf($body, $error_message);
notification([
'type' => SYSTEM_EMAIL,
'uid' => $admin['uid'],
'type' => SYSTEM_EMAIL,
'to_email' => $admin['email'],
'preamble' => $preamble,
'body' => $body,
'body' => $body,
'language' => $lang]
);
}
@ -842,7 +843,7 @@ class DBStructure
public static function definition() {
$a = \Friendica\BaseObject::getApp();
$filename = $a->get_basepath() . '/config/dbstructure.json';
$filename = $a->getBasePath() . '/config/dbstructure.json';
if (!is_readable($filename)) {
throw new Exception('Missing database structure config file config/dbstructure.json');

View file

@ -27,9 +27,6 @@ class PostUpdate
if (!self::update1194()) {
return false;
}
if (!self::update1198()) {
return false;
}
if (!self::update1206()) {
return false;
}
@ -111,88 +108,6 @@ class PostUpdate
logger("Done", LOGGER_DEBUG);
}
/**
* @brief set the author-id and owner-id in all item entries
*
* This job has to be started multiple times until all entries are set.
* It isn't started in the update function since it would consume too much time and can be done in the background.
*
* @return bool "true" when the job is done
*/
private static function update1198()
{
// Was the script completed?
if (Config::get("system", "post_update_version") >= 1198) {
return true;
}
logger("Start", LOGGER_DEBUG);
// Check if the first step is done (Setting "author-id" and "owner-id" in the item table)
$fields = ['author-link', 'author-name', 'author-avatar', 'owner-link', 'owner-name', 'owner-avatar', 'network', 'uid'];
$r = DBA::select('item', $fields, ['author-id' => 0, 'owner-id' => 0], ['limit' => 1000]);
if (!$r) {
// Are there unfinished entries in the thread table?
$r = q("SELECT COUNT(*) AS `total` FROM `thread`
INNER JOIN `item` ON `item`.`id` =`thread`.`iid`
WHERE `thread`.`author-id` = 0 AND `thread`.`owner-id` = 0 AND
(`thread`.`uid` IN (SELECT `uid` from `user`) OR `thread`.`uid` = 0)");
if ($r && ($r[0]["total"] == 0)) {
Config::set("system", "post_update_version", 1198);
logger("Done", LOGGER_DEBUG);
return true;
}
// Update the thread table from the item table
$r = q("UPDATE `thread` INNER JOIN `item` ON `item`.`id`=`thread`.`iid`
SET `thread`.`author-id` = `item`.`author-id`,
`thread`.`owner-id` = `item`.`owner-id`
WHERE `thread`.`author-id` = 0 AND `thread`.`owner-id` = 0 AND
(`thread`.`uid` IN (SELECT `uid` from `user`) OR `thread`.`uid` = 0)");
logger("Updated threads", LOGGER_DEBUG);
if (DBA::isResult($r)) {
Config::set("system", "post_update_version", 1198);
logger("Done", LOGGER_DEBUG);
return true;
}
return false;
}
logger("Query done", LOGGER_DEBUG);
$item_arr = [];
foreach ($r as $item) {
$index = $item["author-link"]."-".$item["owner-link"]."-".$item["uid"];
$item_arr[$index] = ["author-link" => $item["author-link"],
"owner-link" => $item["owner-link"],
"uid" => $item["uid"]];
}
// Set the "author-id" and "owner-id" in the item table and add a new public contact entry if needed
foreach ($item_arr as $item) {
$default = ['url' => $item['author-link'], 'name' => $item['author-name'],
'photo' => $item['author-avatar'], 'network' => $item['network']];
$author_id = Contact::getIdForURL($item["author-link"], 0, false, $default);
$default = ['url' => $item['owner-link'], 'name' => $item['owner-name'],
'photo' => $item['owner-avatar'], 'network' => $item['network']];
$owner_id = Contact::getIdForURL($item["owner-link"], 0, false, $default);
if ($author_id == 0) {
$author_id = -1;
}
if ($owner_id == 0) {
$owner_id = -1;
}
DBA::update('item', ['author-id' => $author_id, 'owner-id' => $owner_id], ['uid' => $item['uid'], 'author-link' => $item['author-link'], 'owner-link' => $item['owner-link'], 'author-id' => 0, 'owner-id' => 0]);
}
logger("Updated items", LOGGER_DEBUG);
return false;
}
/**
* @brief update the "last-item" field in the "self" contact
*

176
src/Model/APContact.php Normal file
View file

@ -0,0 +1,176 @@
<?php
/**
* @file src/Model/APContact.php
*/
namespace Friendica\Model;
use Friendica\BaseObject;
use Friendica\Database\DBA;
use Friendica\Protocol\ActivityPub;
use Friendica\Util\Network;
use Friendica\Util\JsonLD;
use Friendica\Util\DateTimeFormat;
use Friendica\Content\Text\HTML;
require_once 'boot.php';
class APContact extends BaseObject
{
/**
* Resolves the profile url from the address by using webfinger
*
* @param string $addr profile address (user@domain.tld)
* @return string url
*/
private static function addrToUrl($addr)
{
$addr_parts = explode('@', $addr);
if (count($addr_parts) != 2) {
return false;
}
$webfinger = 'https://' . $addr_parts[1] . '/.well-known/webfinger?resource=acct:' . urlencode($addr);
$curlResult = Network::curl($webfinger, false, $redirects, ['accept_content' => 'application/jrd+json,application/json']);
if (!$curlResult->isSuccess() || empty($curlResult->getBody())) {
return false;
}
$data = json_decode($curlResult->getBody(), true);
if (empty($data['links'])) {
return false;
}
foreach ($data['links'] as $link) {
if (empty($link['href']) || empty($link['rel']) || empty($link['type'])) {
continue;
}
if (($link['rel'] == 'self') && ($link['type'] == 'application/activity+json')) {
return $link['href'];
}
}
return false;
}
/**
* Fetches a profile from a given url
*
* @param string $url profile url
* @param boolean $update true = always update, false = never update, null = update when not found
* @return array profile array
*/
public static function getByURL($url, $update = null)
{
if (empty($url)) {
return false;
}
if (empty($update)) {
$apcontact = DBA::selectFirst('apcontact', [], ['url' => $url]);
if (DBA::isResult($apcontact)) {
return $apcontact;
}
$apcontact = DBA::selectFirst('apcontact', [], ['alias' => $url]);
if (DBA::isResult($apcontact)) {
return $apcontact;
}
$apcontact = DBA::selectFirst('apcontact', [], ['addr' => $url]);
if (DBA::isResult($apcontact)) {
return $apcontact;
}
if (!is_null($update)) {
return false;
}
}
if (empty(parse_url($url, PHP_URL_SCHEME))) {
$url = self::addrToUrl($url);
if (empty($url)) {
return false;
}
}
$data = ActivityPub::fetchContent($url);
if (empty($data) || empty($data['id']) || empty($data['inbox'])) {
return false;
}
$apcontact = [];
$apcontact['url'] = $data['id'];
$apcontact['uuid'] = defaults($data, 'diaspora:guid', null);
$apcontact['type'] = defaults($data, 'type', null);
$apcontact['following'] = defaults($data, 'following', null);
$apcontact['followers'] = defaults($data, 'followers', null);
$apcontact['inbox'] = defaults($data, 'inbox', null);
$apcontact['outbox'] = defaults($data, 'outbox', null);
$apcontact['sharedinbox'] = JsonLD::fetchElement($data, 'endpoints', 'sharedInbox');
$apcontact['nick'] = defaults($data, 'preferredUsername', null);
$apcontact['name'] = defaults($data, 'name', $apcontact['nick']);
$apcontact['about'] = HTML::toBBCode(defaults($data, 'summary', ''));
$apcontact['photo'] = JsonLD::fetchElement($data, 'icon', 'url');
$apcontact['alias'] = JsonLD::fetchElement($data, 'url', 'href');
$parts = parse_url($apcontact['url']);
unset($parts['scheme']);
unset($parts['path']);
$apcontact['addr'] = $apcontact['nick'] . '@' . str_replace('//', '', Network::unparseURL($parts));
$apcontact['pubkey'] = trim(JsonLD::fetchElement($data, 'publicKey', 'publicKeyPem'));
// To-Do
// manuallyApprovesFollowers
// Unhandled
// @context, tag, attachment, image, nomadicLocations, signature, following, followers, featured, movedTo, liked
// Unhandled from Misskey
// sharedInbox, isCat
// Unhandled from Kroeg
// kroeg:blocks, updated
// Check if the address is resolvable
if (self::addrToUrl($apcontact['addr']) == $apcontact['url']) {
$parts = parse_url($apcontact['url']);
unset($parts['path']);
$apcontact['baseurl'] = Network::unparseURL($parts);
} else {
$apcontact['addr'] = null;
$apcontact['baseurl'] = null;
}
if ($apcontact['url'] == $apcontact['alias']) {
$apcontact['alias'] = null;
}
$apcontact['updated'] = DateTimeFormat::utcNow();
DBA::update('apcontact', $apcontact, ['url' => $url], true);
// Update some data in the contact table with various ways to catch them all
$contact_fields = ['name' => $apcontact['name'], 'about' => $apcontact['about']];
DBA::update('contact', $contact_fields, ['nurl' => normalise_link($url)]);
$contacts = DBA::select('contact', ['uid', 'id'], ['nurl' => normalise_link($url)]);
while ($contact = DBA::fetch($contacts)) {
Contact::updateAvatar($apcontact['photo'], $contact['uid'], $contact['id']);
}
DBA::close($contacts);
// Update the gcontact table
DBA::update('gcontact', $contact_fields, ['nurl' => normalise_link($url)]);
logger('Updated profile for ' . $url, LOGGER_DEBUG);
return $apcontact;
}
}

View file

@ -16,7 +16,9 @@ use Friendica\Database\DBA;
use Friendica\Model\Profile;
use Friendica\Network\Probe;
use Friendica\Object\Image;
use Friendica\Protocol\ActivityPub;
use Friendica\Protocol\Diaspora;
use Friendica\Protocol\DFRN;
use Friendica\Protocol\OStatus;
use Friendica\Protocol\PortableContact;
use Friendica\Protocol\Salmon;
@ -519,7 +521,7 @@ class Contact extends BaseObject
}
// Archive the contact
DBA::update('contact', ['archive' => true, 'network' => Protocol::PHANTOM], ['id' => $id]);
DBA::update('contact', ['archive' => true, 'network' => Protocol::PHANTOM, 'deleted' => true], ['id' => $id]);
// Delete it in the background
Worker::add(PRIORITY_LOW, 'RemoveContact', $id);
@ -528,13 +530,16 @@ class Contact extends BaseObject
/**
* @brief Sends an unfriend message. Does not remove the contact
*
* @param array $user User unfriending
* @param array $contact Contact unfriended
* @param array $user User unfriending
* @param array $contact Contact unfriended
* @param boolean $dissolve Remove the contact on the remote side
* @return void
*/
public static function terminateFriendship(array $user, array $contact)
public static function terminateFriendship(array $user, array $contact, $dissolve = false)
{
if (in_array($contact['network'], [Protocol::OSTATUS, Protocol::DFRN])) {
if (($contact['network'] == Protocol::DFRN) && $dissolve) {
DFRN::deliver($user, $contact, 'placeholder', true);
} elseif (in_array($contact['network'], [Protocol::OSTATUS, Protocol::DFRN])) {
// create an unfollow slap
$item = [];
$item['verb'] = NAMESPACE_OSTATUS . "/unfollow";
@ -551,6 +556,12 @@ class Contact extends BaseObject
}
} elseif ($contact['network'] == Protocol::DIASPORA) {
Diaspora::sendUnshare($user, $contact);
} elseif ($contact['network'] == Protocol::ACTIVITYPUB) {
ActivityPub\Transmitter::sendContactUndo($contact['url'], $user['uid']);
if ($dissolve) {
ActivityPub\Transmitter::sendContactReject($contact['url'], $contact['hub-verify'], $user['uid']);
}
}
}
@ -771,7 +782,7 @@ class Contact extends BaseObject
}
if ((empty($profile["addr"]) || empty($profile["name"])) && (defaults($profile, "gid", 0) != 0)
&& in_array($profile["network"], [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])
&& in_array($profile["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])
) {
Worker::add(PRIORITY_LOW, "UpdateGContact", $profile["gid"]);
}
@ -814,7 +825,7 @@ class Contact extends BaseObject
// Fetch contact data from the contact table for the given user
$r = q("SELECT `id`, `id` AS `cid`, 0 AS `gid`, 0 AS `zid`, `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`, `xmpp`,
`keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, `self`
FROM `contact` WHERE `addr` = '%s' AND `uid` = %d",
FROM `contact` WHERE `addr` = '%s' AND `uid` = %d AND NOT `deleted`",
DBA::escape($addr),
intval($uid)
);
@ -822,7 +833,7 @@ class Contact extends BaseObject
if (!DBA::isResult($r)) {
$r = q("SELECT `id`, 0 AS `cid`, `id` AS `zid`, 0 AS `gid`, `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`, `xmpp`,
`keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, 0 AS `self`
FROM `contact` WHERE `addr` = '%s' AND `uid` = 0",
FROM `contact` WHERE `addr` = '%s' AND `uid` = 0 AND NOT `deleted`",
DBA::escape($addr)
);
}
@ -912,12 +923,12 @@ class Contact extends BaseObject
$poke_link = System::baseUrl() . '/poke/?f=&c=' . $contact['id'];
}
$contact_url = System::baseUrl() . '/contacts/' . $contact['id'];
$contact_url = System::baseUrl() . '/contact/' . $contact['id'];
$posts_link = System::baseUrl() . '/contacts/' . $contact['id'] . '/conversations';
$posts_link = System::baseUrl() . '/contact/' . $contact['id'] . '/conversations';
if (!$contact['self']) {
$contact_drop_link = System::baseUrl() . '/contacts/' . $contact['id'] . '/drop?confirm=1';
$contact_drop_link = System::baseUrl() . '/contact/' . $contact['id'] . '/drop?confirm=1';
}
/**
@ -1010,10 +1021,11 @@ class Contact extends BaseObject
* @param integer $uid The user id for the contact (0 = public contact)
* @param boolean $no_update Don't update the contact
* @param array $default Default value for creating the contact when every else fails
* @param boolean $in_loop Internally used variable to prevent an endless loop
*
* @return integer Contact ID
*/
public static function getIdForURL($url, $uid = 0, $no_update = false, $default = [])
public static function getIdForURL($url, $uid = 0, $no_update = false, $default = [], $in_loop = false)
{
logger("Get contact data for url " . $url . " and user " . $uid . " - " . System::callstack(), LOGGER_DEBUG);
@ -1025,18 +1037,18 @@ class Contact extends BaseObject
/// @todo Verify if we can't use Contact::getDetailsByUrl instead of the following
// We first try the nurl (http://server.tld/nick), most common case
$contact = DBA::selectFirst('contact', ['id', 'avatar', 'avatar-date'], ['nurl' => normalise_link($url), 'uid' => $uid]);
$contact = DBA::selectFirst('contact', ['id', 'avatar', 'avatar-date'], ['nurl' => normalise_link($url), 'uid' => $uid, 'deleted' => false]);
// Then the addr (nick@server.tld)
if (!DBA::isResult($contact)) {
$contact = DBA::selectFirst('contact', ['id', 'avatar', 'avatar-date'], ['addr' => $url, 'uid' => $uid]);
$contact = DBA::selectFirst('contact', ['id', 'avatar', 'avatar-date'], ['addr' => $url, 'uid' => $uid, 'deleted' => false]);
}
// Then the alias (which could be anything)
if (!DBA::isResult($contact)) {
// The link could be provided as http although we stored it as https
$ssl_url = str_replace('http://', 'https://', $url);
$condition = ['`alias` IN (?, ?, ?) AND `uid` = ?', $url, normalise_link($url), $ssl_url, $uid];
$condition = ['`alias` IN (?, ?, ?) AND `uid` = ? AND NOT `deleted`', $url, normalise_link($url), $ssl_url, $uid];
$contact = DBA::selectFirst('contact', ['id', 'avatar', 'avatar-date'], $condition);
}
@ -1050,7 +1062,6 @@ class Contact extends BaseObject
if (!x($contact, 'avatar')) {
$update_contact = true;
}
if (!$update_contact || $no_update) {
return $contact_id;
}
@ -1076,10 +1087,15 @@ class Contact extends BaseObject
if (empty($data)) {
$data = Probe::uri($url, "", $uid);
// Ensure that there is a gserver entry
if (!empty($data['baseurl']) && ($data['network'] != Protocol::PHANTOM)) {
PortableContact::checkServer($data['baseurl']);
}
}
// Last try in gcontact for unsupported networks
if (!in_array($data["network"], [Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA, Protocol::PUMPIO, Protocol::MAIL, Protocol::FEED])) {
if (!in_array($data["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA, Protocol::PUMPIO, Protocol::MAIL, Protocol::FEED])) {
if ($uid != 0) {
return 0;
}
@ -1123,8 +1139,8 @@ class Contact extends BaseObject
}
}
if (!$contact_id && ($data["alias"] != '') && ($data["alias"] != $url)) {
$contact_id = self::getIdForURL($data["alias"], $uid, true);
if (!$contact_id && ($data["alias"] != '') && ($data["alias"] != $url) && !$in_loop) {
$contact_id = self::getIdForURL($data["alias"], $uid, true, $default, true);
}
$url = $data["url"];
@ -1307,33 +1323,27 @@ class Contact extends BaseObject
require_once 'include/conversation.php';
// There are no posts with "uid = 0" with connector networks
// This speeds up the query a lot
$r = q("SELECT `network`, `id` AS `author-id`, `contact-type` FROM `contact`
WHERE `contact`.`nurl` = '%s' AND `contact`.`uid` = 0",
DBA::escape(normalise_link($contact_url))
);
$cid = Self::getIdForURL($contact_url);
if (!DBA::isResult($r)) {
$contact = DBA::selectFirst('contact', ['contact-type', 'network'], ['id' => $cid]);
if (!DBA::isResult($contact)) {
return '';
}
if (in_array($r[0]["network"], [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, ""])) {
if (in_array($contact["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, ""])) {
$sql = "(`item`.`uid` = 0 OR (`item`.`uid` = ? AND NOT `item`.`global`))";
} else {
$sql = "`item`.`uid` = ?";
}
$author_id = intval($r[0]["author-id"]);
$contact = ($r[0]["contact-type"] == self::ACCOUNT_TYPE_COMMUNITY ? 'owner-id' : 'author-id');
$contact_field = ($contact["contact-type"] == self::ACCOUNT_TYPE_COMMUNITY ? 'owner-id' : 'author-id');
if ($thread_mode) {
$condition = ["`$contact` = ? AND `gravity` = ? AND " . $sql,
$author_id, GRAVITY_PARENT, local_user()];
$condition = ["`$contact_field` = ? AND `gravity` = ? AND " . $sql,
$cid, GRAVITY_PARENT, local_user()];
} else {
$condition = ["`$contact` = ? AND `gravity` IN (?, ?) AND " . $sql,
$author_id, GRAVITY_PARENT, GRAVITY_COMMENT, local_user()];
$condition = ["`$contact_field` = ? AND `gravity` IN (?, ?) AND " . $sql,
$cid, GRAVITY_PARENT, GRAVITY_COMMENT, local_user()];
}
$params = ['order' => ['created' => true],
@ -1486,10 +1496,11 @@ class Contact extends BaseObject
}
/**
* @param integer $id contact id
* @param integer $id contact id
* @param string $network Optional network we are probing for
* @return boolean
*/
public static function updateFromProbe($id)
public static function updateFromProbe($id, $network = '')
{
/*
Warning: Never ever fetch the public key via Probe::uri and write it into the contacts.
@ -1502,10 +1513,10 @@ class Contact extends BaseObject
return false;
}
$ret = Probe::uri($contact["url"]);
$ret = Probe::uri($contact["url"], $network);
// If Probe::uri fails the network code will be different
if ($ret["network"] != $contact["network"]) {
if (($ret["network"] != $contact["network"]) && !in_array($ret["network"], [Protocol::ACTIVITYPUB, $network])) {
return false;
}
@ -1528,14 +1539,15 @@ class Contact extends BaseObject
DBA::update(
'contact', [
'url' => $ret['url'],
'nurl' => normalise_link($ret['url']),
'addr' => $ret['addr'],
'alias' => $ret['alias'],
'batch' => $ret['batch'],
'notify' => $ret['notify'],
'poll' => $ret['poll'],
'poco' => $ret['poco']
'url' => $ret['url'],
'nurl' => normalise_link($ret['url']),
'network' => $ret['network'],
'addr' => $ret['addr'],
'alias' => $ret['alias'],
'batch' => $ret['batch'],
'notify' => $ret['notify'],
'poll' => $ret['poll'],
'poco' => $ret['poco']
],
['id' => $id]
);
@ -1621,10 +1633,10 @@ class Contact extends BaseObject
if (($ret['network'] === Protocol::DFRN) && !DBA::isResult($contact)) {
if ($interactive) {
if (strlen($a->urlpath)) {
if (strlen($a->getURLPath())) {
$myaddr = bin2hex(System::baseUrl() . '/profile/' . $a->user['nickname']);
} else {
$myaddr = bin2hex($a->user['nickname'] . '@' . $a->get_hostname());
$myaddr = bin2hex($a->user['nickname'] . '@' . $a->getHostName());
}
goaway($ret['request'] . "&addr=$myaddr");
@ -1677,7 +1689,7 @@ class Contact extends BaseObject
$hidden = (($ret['network'] === Protocol::MAIL) ? 1 : 0);
if (in_array($ret['network'], [Protocol::MAIL, Protocol::DIASPORA])) {
if (in_array($ret['network'], [Protocol::MAIL, Protocol::DIASPORA, Protocol::ACTIVITYPUB])) {
$writeable = 1;
}
@ -1757,6 +1769,9 @@ class Contact extends BaseObject
} elseif ($contact['network'] == Protocol::DIASPORA) {
$ret = Diaspora::sendShare($a->user, $contact);
logger('share returns: ' . $ret);
} elseif ($contact['network'] == Protocol::ACTIVITYPUB) {
$ret = ActivityPub\Transmitter::sendActivity('Follow', $contact['url'], $uid);
logger('Follow returns: ' . $ret);
}
}
@ -1805,7 +1820,7 @@ class Contact extends BaseObject
return $contact;
}
public static function addRelationship($importer, $contact, $datarray, $item, $sharing = false) {
public static function addRelationship($importer, $contact, $datarray, $item = '', $sharing = false) {
// Should always be set
if (empty($datarray['author-id'])) {
return;
@ -1818,7 +1833,7 @@ class Contact extends BaseObject
return;
}
$url = $pub_contact['url'];
$url = defaults($datarray, 'author-link', $pub_contact['url']);
$name = $pub_contact['name'];
$photo = $pub_contact['photo'];
$nick = $pub_contact['nick'];
@ -1830,13 +1845,17 @@ class Contact extends BaseObject
DBA::update('contact', ['rel' => self::FRIEND, 'writable' => true],
['id' => $contact['id'], 'uid' => $importer['uid']]);
}
if ($contact['network'] == Protocol::ACTIVITYPUB) {
ActivityPub\Transmitter::sendContactAccept($contact['url'], $contact['hub-verify'], $importer['uid']);
}
// send email notification to owner?
} else {
if (DBA::exists('contact', ['nurl' => normalise_link($url), 'uid' => $importer['uid'], 'pending' => true])) {
logger('ignoring duplicated connection request from pending contact ' . $url);
return;
}
// create contact record
q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
`blocked`, `readonly`, `pending`, `writable`)
@ -1899,6 +1918,12 @@ class Contact extends BaseObject
} elseif (DBA::isResult($user) && in_array($user['page-flags'], [self::PAGE_SOAPBOX, self::PAGE_FREELOVE, self::PAGE_COMMUNITY])) {
$condition = ['uid' => $importer['uid'], 'url' => $url, 'pending' => true];
DBA::update('contact', ['pending' => false], $condition);
$contact = DBA::selectFirst('contact', ['url', 'network', 'hub-verify'], ['id' => $contact_record['id']]);
if ($contact['network'] == Protocol::ACTIVITYPUB) {
ActivityPub\Transmitter::sendContactAccept($contact['url'], $contact['hub-verify'], $importer['uid']);
}
}
}
}

View file

@ -17,13 +17,14 @@ class Conversation
* These constants represent the parcel format used to transport a conversation independently of the message protocol.
* It currently is stored in the "protocol" field for legacy reasons.
*/
const PARCEL_UNKNOWN = 0;
const PARCEL_ACTIVITYPUB = 0;
const PARCEL_DFRN = 1;
const PARCEL_DIASPORA = 2;
const PARCEL_SALMON = 3;
const PARCEL_FEED = 4; // Deprecated
const PARCEL_SPLIT_CONVERSATION = 6;
const PARCEL_TWITTER = 67;
const PARCEL_UNKNOWN = 255;
/**
* @brief Store the conversation data
@ -34,7 +35,7 @@ class Conversation
public static function insert(array $arr)
{
if (in_array(defaults($arr, 'network', Protocol::PHANTOM),
[Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, Protocol::TWITTER]) && !empty($arr['uri'])) {
[Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, Protocol::TWITTER]) && !empty($arr['uri'])) {
$conversation = ['item-uri' => $arr['uri'], 'received' => DateTimeFormat::utcNow()];
if (isset($arr['parent-uri']) && ($arr['parent-uri'] != $arr['uri'])) {
@ -70,7 +71,8 @@ class Conversation
unset($old_conv['source']);
}
// Update structure data all the time but the source only when its from a better protocol.
if (isset($conversation['protocol']) && isset($conversation['source']) && ($old_conv['protocol'] < $conversation['protocol']) && ($old_conv['protocol'] != 0)) {
if (empty($conversation['source']) || (!empty($old_conv['source']) &&
($old_conv['protocol'] < defaults($conversation, 'protocol', PARCEL_UNKNOWN)))) {
unset($conversation['protocol']);
unset($conversation['source']);
}

View file

@ -12,6 +12,7 @@ use Friendica\Core\L10n;
use Friendica\Core\PConfig;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Map;
@ -313,7 +314,7 @@ class Event extends BaseObject
Addon::callHooks('event_updated', $event['id']);
} else {
$event['guid'] = defaults($arr, 'guid', System::createGUID(32));
$event['guid'] = defaults($arr, 'guid', System::createUUID());
// New event. Store it.
DBA::insert('event', $event);
@ -589,6 +590,12 @@ class Event extends BaseObject
$title = strip_tags(html_entity_decode($title, ENT_QUOTES, 'UTF-8'));
}
$author_link = $event['author-link'];
$plink = $event['plink'];
$event['author-link'] = Contact::magicLink($author_link);
$event['plink'] = Contact::magicLink($author_link, $plink);
$html = self::getHTML($event);
$event['desc'] = BBCode::convert($event['desc']);
$event['location'] = BBCode::convert($event['location']);

View file

@ -61,11 +61,11 @@ class GContact
$search .= "%";
$results = DBA::p("SELECT `nurl` FROM `gcontact`
WHERE NOT `hide` AND `network` IN (?, ?, ?) AND
WHERE NOT `hide` AND `network` IN (?, ?, ?, ?) AND
((`last_contact` >= `last_failure`) OR (`updated` >= `last_failure`)) AND
(`addr` LIKE ? OR `name` LIKE ? OR `nick` LIKE ?) $extra_sql
GROUP BY `nurl` ORDER BY `nurl` DESC LIMIT 1000",
Protocol::DFRN, $ostatus, $diaspora, $search, $search, $search
Protocol::DFRN, Protocol::ACTIVITYPUB, $ostatus, $diaspora, $search, $search, $search
);
$gcontacts = [];
@ -138,7 +138,7 @@ class GContact
}
// Assure that there are no parameter fragments in the profile url
if (in_array($gcontact['network'], [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, ""])) {
if (in_array($gcontact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, ""])) {
$gcontact['url'] = self::cleanContactUrl($gcontact['url']);
}
@ -214,7 +214,7 @@ class GContact
throw new Exception('No name and photo for URL '.$gcontact['url']);
}
if (!in_array($gcontact['network'], [Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA])) {
if (!in_array($gcontact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA])) {
throw new Exception('No federated network ('.$gcontact['network'].') detected for URL '.$gcontact['url']);
}
@ -651,7 +651,7 @@ class GContact
self::fixAlternateContactAddress($contact);
// Remove unwanted parts from the contact url (e.g. "?zrl=...")
if (in_array($contact["network"], [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
if (in_array($contact["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
$contact["url"] = self::cleanContactUrl($contact["url"]);
}
@ -957,12 +957,12 @@ class GContact
$url = $server."/main/statistics";
$result = Network::curl($url);
if (!$result["success"]) {
$curlResult = Network::curl($url);
if (!$curlResult->isSuccess()) {
return false;
}
$statistics = json_decode($result["body"]);
$statistics = json_decode($curlResult->getBody());
if (!empty($statistics->config)) {
if ($statistics->config->instance_with_ssl) {

View file

@ -349,7 +349,7 @@ class Group extends BaseObject
* @param int $cid
* @return string
*/
public static function sidebarWidget($every = 'contacts', $each = 'group', $editmode = 'standard', $group_id = '', $cid = 0)
public static function sidebarWidget($every = 'contact', $each = 'group', $editmode = 'standard', $group_id = '', $cid = 0)
{
$o = '';
@ -404,7 +404,7 @@ class Group extends BaseObject
'newgroup' => $editmode == 'extended' || $editmode == 'full' ? 1 : '',
'grouppage' => 'group/',
'$edittext' => L10n::t('Edit group'),
'$ungrouped' => $every === 'contacts' ? L10n::t('Contacts not in any group') : '',
'$ungrouped' => $every === 'contact' ? L10n::t('Contacts not in any group') : '',
'$ungrouped_selected' => (($group_id === 'none') ? 'group-selected' : ''),
'$createtext' => L10n::t('Create a new group'),
'$creategroup' => L10n::t('Group Name: '),

View file

@ -80,7 +80,7 @@ class Item extends BaseObject
// All fields in the item table
const ITEM_FIELDLIST = ['id', 'uid', 'parent', 'uri', 'parent-uri', 'thr-parent', 'guid',
'contact-id', 'type', 'wall', 'gravity', 'extid', 'icid', 'iaid', 'psid',
'uri-hash', 'created', 'edited', 'commented', 'received', 'changed', 'verb',
'created', 'edited', 'commented', 'received', 'changed', 'verb',
'postopts', 'plink', 'resource-id', 'event-id', 'tag', 'attach', 'inform',
'file', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'post-type',
'private', 'pubmail', 'moderated', 'visible', 'starred', 'bookmark',
@ -176,7 +176,7 @@ class Item extends BaseObject
// We can always comment on posts from these networks
if (array_key_exists('writable', $row) &&
in_array($row['internal-network'], [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
in_array($row['internal-network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
$row['writable'] = true;
}
@ -231,6 +231,10 @@ class Item extends BaseObject
}
}
if (array_key_exists('signed_text', $row) && array_key_exists('interaction', $row) && !is_null($row['interaction'])) {
$row['signed_text'] = $row['interaction'];
}
if (array_key_exists('ignored', $row) && array_key_exists('internal-user-ignored', $row) && !is_null($row['internal-user-ignored'])) {
$row['ignored'] = $row['internal-user-ignored'];
}
@ -242,6 +246,7 @@ class Item extends BaseObject
unset($row['internal-iaid']);
unset($row['internal-icid']);
unset($row['internal-user-ignored']);
unset($row['interaction']);
return $row;
}
@ -375,7 +380,7 @@ class Item extends BaseObject
$usermode = true;
}
$fields = self::fieldlist($selected, $usermode);
$fields = self::fieldlist($usermode);
$select_fields = self::constructSelectFields($fields, $selected);
@ -482,7 +487,7 @@ class Item extends BaseObject
$usermode = true;
}
$fields = self::fieldlist($selected, $usermode);
$fields = self::fieldlist($usermode);
$fields['thread'] = ['mention', 'ignored', 'iid'];
@ -518,13 +523,13 @@ class Item extends BaseObject
*
* @return array field list
*/
private static function fieldlist($selected, $usermode)
private static function fieldlist($usermode)
{
$fields = [];
$fields['item'] = ['id', 'uid', 'parent', 'uri', 'parent-uri', 'thr-parent', 'guid',
'contact-id', 'owner-id', 'author-id', 'type', 'wall', 'gravity', 'extid',
'created', 'edited', 'commented', 'received', 'changed', 'psid', 'uri-hash',
'created', 'edited', 'commented', 'received', 'changed', 'psid',
'resource-id', 'event-id', 'tag', 'attach', 'post-type', 'file',
'private', 'pubmail', 'moderated', 'visible', 'starred', 'bookmark',
'unseen', 'deleted', 'origin', 'forum_mode', 'mention', 'global',
@ -567,6 +572,8 @@ class Item extends BaseObject
$fields['sign'] = ['signed_text', 'signature', 'signer'];
$fields['diaspora-interaction'] = ['interaction'];
return $fields;
}
@ -653,12 +660,16 @@ class Item extends BaseObject
$joins .= " LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`";
}
if (strpos($sql_commands, "`diaspora-interaction`.") !== false) {
$joins .= " LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `item`.`uri-id`";
}
if (strpos($sql_commands, "`item-activity`.") !== false) {
$joins .= " LEFT JOIN `item-activity` ON `item-activity`.`id` = `item`.`iaid`";
$joins .= " LEFT JOIN `item-activity` ON `item-activity`.`uri-id` = `item`.`uri-id`";
}
if (strpos($sql_commands, "`item-content`.") !== false) {
$joins .= " LEFT JOIN `item-content` ON `item-content`.`id` = `item`.`icid`";
$joins .= " LEFT JOIN `item-content` ON `item-content`.`uri-id` = `item`.`uri-id`";
}
if (strpos($sql_commands, "`item-delivery-data`.") !== false) {
@ -705,6 +716,10 @@ class Item extends BaseObject
$selected[] = 'internal-user-ignored';
}
if (in_array('signed_text', $selected)) {
$selected[] = 'interaction';
}
$selection = [];
foreach ($fields as $table => $table_fields) {
foreach ($table_fields as $field => $select) {
@ -750,19 +765,6 @@ class Item extends BaseObject
return $query;
}
/**
* @brief Generate a server unique item hash for linking between the item tables
*
* @param string $uri Item URI
* @param date $created Item creation date
*
* @return string the item hash
*/
private static function itemHash($uri, $created)
{
return round(strtotime($created) / 100) . hash('ripemd128', $uri);
}
/**
* @brief Update existing item entries
*
@ -788,7 +790,7 @@ class Item extends BaseObject
// We cannot simply expand the condition to check for origin entries
// The condition needn't to be a simple array but could be a complex condition.
// And we have to execute this query before the update to ensure to fetch the same data.
$items = DBA::select('item', ['id', 'origin', 'uri', 'created', 'uri-hash', 'iaid', 'icid', 'tag', 'file'], $condition);
$items = DBA::select('item', ['id', 'origin', 'uri', 'uri-id', 'iaid', 'icid', 'tag', 'file'], $condition);
$content_fields = [];
foreach (array_merge(self::CONTENT_FIELDLIST, self::MIXED_CONTENT_FIELDLIST) as $field) {
@ -843,34 +845,11 @@ class Item extends BaseObject
$rows = DBA::affectedRows();
while ($item = DBA::fetch($items)) {
// This part here can safely be removed when the legacy fields in the item had been removed
if (empty($item['uri-hash']) && !empty($item['uri']) && !empty($item['created'])) {
// Fetch the uri-hash from an existing item entry if there is one
$item_condition = ["`uri` = ? AND `uri-hash` != ''", $item['uri']];
$existing = DBA::selectfirst('item', ['uri-hash'], $item_condition);
if (DBA::isResult($existing)) {
$item['uri-hash'] = $existing['uri-hash'];
} else {
$item['uri-hash'] = self::itemHash($item['uri'], $item['created']);
}
DBA::update('item', ['uri-hash' => $item['uri-hash']], ['id' => $item['id']]);
DBA::update('item-activity', ['uri-hash' => $item['uri-hash']], ["`uri` = ? AND `uri-hash` = ''", $item['uri']]);
DBA::update('item-content', ['uri-plink-hash' => $item['uri-hash']], ["`uri` = ? AND `uri-plink-hash` = ''", $item['uri']]);
}
if (!empty($item['iaid']) || (!empty($content_fields['verb']) && (self::activityToIndex($content_fields['verb']) >= 0))) {
if (!empty($item['iaid'])) {
$update_condition = ['id' => $item['iaid']];
} else {
$update_condition = ['uri-hash' => $item['uri-hash']];
}
self::updateActivity($content_fields, $update_condition);
self::updateActivity($content_fields, ['uri-id' => $item['uri-id']]);
if (empty($item['iaid'])) {
$item_activity = DBA::selectFirst('item-activity', ['id'], ['uri-hash' => $item['uri-hash']]);
$item_activity = DBA::selectFirst('item-activity', ['id'], ['uri-id' => $item['uri-id']]);
if (DBA::isResult($item_activity)) {
$item_fields = ['iaid' => $item_activity['id'], 'icid' => null];
foreach (self::MIXED_CONTENT_FIELDLIST as $field) {
@ -894,15 +873,10 @@ class Item extends BaseObject
}
}
} else {
if (!empty($item['icid'])) {
$update_condition = ['id' => $item['icid']];
} else {
$update_condition = ['uri-plink-hash' => $item['uri-hash']];
}
self::updateContent($content_fields, $update_condition);
self::updateContent($content_fields, ['uri-id' => $item['uri-id']]);
if (empty($item['icid'])) {
$item_content = DBA::selectFirst('item-content', [], ['uri-plink-hash' => $item['uri-hash']]);
$item_content = DBA::selectFirst('item-content', [], ['uri-id' => $item['uri-id']]);
if (DBA::isResult($item_content)) {
$item_fields = ['icid' => $item_content['id']];
// Clear all fields in the item table that have a content in the item-content table
@ -1081,9 +1055,8 @@ class Item extends BaseObject
DBA::delete('item-delivery-data', ['iid' => $item['id']]);
if (!empty($item['iaid']) && !self::exists(['iaid' => $item['iaid'], 'deleted' => false])) {
DBA::delete('item-activity', ['id' => $item['iaid']], ['cascade' => false]);
}
// We don't delete the item-activity here, since we need some of the data for ActivityPub
if (!empty($item['icid']) && !self::exists(['icid' => $item['icid'], 'deleted' => false])) {
DBA::delete('item-content', ['id' => $item['icid']], ['cascade' => false]);
}
@ -1167,7 +1140,7 @@ class Item extends BaseObject
if ($notify) {
// We have to avoid duplicates. So we create the GUID in form of a hash of the plink or uri.
// We add the hash of our own host because our host is the original creator of the post.
$prefix_host = get_app()->get_hostname();
$prefix_host = get_app()->getHostName();
} else {
$prefix_host = '';
@ -1205,7 +1178,7 @@ class Item extends BaseObject
} elseif (!empty($item['uri'])) {
$guid = self::guidFromUri($item['uri'], $prefix_host);
} else {
$guid = System::createGUID(32, hash('crc32', $prefix_host));
$guid = System::createUUID(hash('crc32', $prefix_host));
}
return $guid;
@ -1290,17 +1263,16 @@ class Item extends BaseObject
*/
$dsprsig = null;
if (x($item, 'dsprsig')) {
if (isset($item['dsprsig'])) {
$encoded_signature = $item['dsprsig'];
$dsprsig = json_decode(base64_decode($item['dsprsig']));
unset($item['dsprsig']);
}
if (!empty($item['diaspora_signed_text'])) {
$diaspora_signed_text = '';
if (isset($item['diaspora_signed_text'])) {
$diaspora_signed_text = $item['diaspora_signed_text'];
unset($item['diaspora_signed_text']);
} else {
$diaspora_signed_text = '';
}
// Converting the plink
@ -1352,7 +1324,7 @@ class Item extends BaseObject
* We have to check several networks since Friendica posts could be repeated
* via OStatus (maybe Diasporsa as well)
*/
if (in_array($item['network'], [Protocol::DIASPORA, Protocol::DFRN, Protocol::OSTATUS, ""])) {
if (in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::DIASPORA, Protocol::DFRN, Protocol::OSTATUS, ""])) {
$condition = ["`uri` = ? AND `uid` = ? AND `network` IN (?, ?, ?)",
trim($item['uri']), $item['uid'],
Protocol::DIASPORA, Protocol::DFRN, Protocol::OSTATUS];
@ -1367,13 +1339,6 @@ class Item extends BaseObject
}
}
// Ensure to always have the same creation date.
$existing = self::selectfirst(['created', 'uri-hash'], ['uri' => $item['uri']]);
if (DBA::isResult($existing)) {
$item['created'] = $existing['created'];
$item['uri-hash'] = $existing['uri-hash'];
}
$item['wall'] = intval(defaults($item, 'wall', 0));
$item['extid'] = trim(defaults($item, 'extid', ''));
$item['author-name'] = trim(defaults($item, 'author-name', ''));
@ -1416,9 +1381,6 @@ class Item extends BaseObject
$item['inform'] = trim(defaults($item, 'inform', ''));
$item['file'] = trim(defaults($item, 'file', ''));
// Unique identifier to be linked against item-activities and item-content
$item['uri-hash'] = defaults($item, 'uri-hash', self::itemHash($item['uri'], $item['created']));
// When there is no content then we don't post it
if ($item['body'].$item['title'] == '') {
logger('No body, no title.');
@ -1462,15 +1424,6 @@ class Item extends BaseObject
return 0;
}
// These fields aren't stored anymore in the item table, they are fetched upon request
unset($item['author-link']);
unset($item['author-name']);
unset($item['author-avatar']);
unset($item['owner-link']);
unset($item['owner-name']);
unset($item['owner-avatar']);
if ($item['network'] == Protocol::PHANTOM) {
logger('Missing network. Called by: '.System::callstack(), LOGGER_DEBUG);
@ -1708,6 +1661,15 @@ class Item extends BaseObject
unset($item['postopts']);
unset($item['inform']);
// These fields aren't stored anymore in the item table, they are fetched upon request
unset($item['author-link']);
unset($item['author-name']);
unset($item['author-avatar']);
unset($item['owner-link']);
unset($item['owner-name']);
unset($item['owner-avatar']);
DBA::transaction();
$ret = DBA::insert('item', $item);
@ -1799,14 +1761,17 @@ class Item extends BaseObject
logger("Repaired double encoded signature from handle ".$dsprsig->signer, LOGGER_DEBUG);
}
DBA::insert('sign', ['iid' => $current_post, 'signed_text' => $dsprsig->signed_text,
'signature' => $dsprsig->signature, 'signer' => $dsprsig->signer]);
if (!empty($dsprsig->signed_text) && empty($dsprsig->signature) && empty($dsprsig->signer)) {
DBA::insert('diaspora-interaction', ['uri-id' => $item['uri-id'], 'interaction' => $dsprsig->signed_text], true);
} else {
// The other fields are used by very old Friendica servers, so we currently store them differently
DBA::insert('sign', ['iid' => $current_post, 'signed_text' => $dsprsig->signed_text,
'signature' => $dsprsig->signature, 'signer' => $dsprsig->signer]);
}
}
if (!empty($diaspora_signed_text)) {
// Formerly we stored the signed text, the signature and the author in different fields.
// We now store the raw data so that we are more flexible.
DBA::insert('sign', ['iid' => $current_post, 'signed_text' => $diaspora_signed_text]);
DBA::insert('diaspora-interaction', ['uri-id' => $item['uri-id'], 'interaction' => $diaspora_signed_text], true);
}
$deleted = self::tagDeliver($item['uid'], $current_post);
@ -1911,8 +1876,7 @@ class Item extends BaseObject
return false;
}
$fields = ['uri' => $item['uri'], 'activity' => $activity_index,
'uri-hash' => $item['uri-hash'], 'uri-id' => $item['uri-id']];
$fields = ['activity' => $activity_index, 'uri-hash' => (string)$item['uri-id'], 'uri-id' => $item['uri-id']];
// We just remove everything that is content
foreach (array_merge(self::CONTENT_FIELDLIST, self::MIXED_CONTENT_FIELDLIST) as $field) {
@ -1926,7 +1890,7 @@ class Item extends BaseObject
}
// Do we already have this content?
$item_activity = DBA::selectFirst('item-activity', ['id'], ['uri-hash' => $item['uri-hash']]);
$item_activity = DBA::selectFirst('item-activity', ['id'], ['uri-id' => $item['uri-id']]);
if (DBA::isResult($item_activity)) {
$item['iaid'] = $item_activity['id'];
logger('Fetched activity for URI ' . $item['uri'] . ' (' . $item['iaid'] . ')');
@ -1936,6 +1900,7 @@ class Item extends BaseObject
} else {
// This shouldn't happen.
logger('Could not insert activity for URI ' . $item['uri'] . ' - should not happen');
Lock::release('item_insert_activity');
return false;
}
if ($locked) {
@ -1951,8 +1916,7 @@ class Item extends BaseObject
*/
private static function insertContent(&$item)
{
$fields = ['uri' => $item['uri'], 'uri-plink-hash' => $item['uri-hash'],
'uri-id' => $item['uri-id']];
$fields = ['uri-plink-hash' => (string)$item['uri-id'], 'uri-id' => $item['uri-id']];
foreach (array_merge(self::CONTENT_FIELDLIST, self::MIXED_CONTENT_FIELDLIST) as $field) {
if (isset($item[$field])) {
@ -1968,7 +1932,7 @@ class Item extends BaseObject
}
// Do we already have this content?
$item_content = DBA::selectFirst('item-content', ['id'], ['uri-plink-hash' => $item['uri-hash']]);
$item_content = DBA::selectFirst('item-content', ['id'], ['uri-id' => $item['uri-id']]);
if (DBA::isResult($item_content)) {
$item['icid'] = $item_content['id'];
logger('Fetched content for URI ' . $item['uri'] . ' (' . $item['icid'] . ')');
@ -2053,7 +2017,7 @@ class Item extends BaseObject
// Only distribute public items from native networks
$condition = ['id' => $itemid, 'uid' => 0,
'network' => [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, ""],
'network' => [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, ""],
'visible' => true, 'deleted' => false, 'moderated' => false, 'private' => false];
$item = self::selectFirst(self::ITEM_FIELDLIST, ['id' => $itemid]);
if (!DBA::isResult($item)) {
@ -2071,14 +2035,46 @@ class Item extends BaseObject
$users = [];
$condition = ["`nurl` IN (SELECT `nurl` FROM `contact` WHERE `id` = ?) AND `uid` != 0 AND NOT `blocked` AND `rel` IN (?, ?)",
$parent['owner-id'], Contact::SHARING, Contact::FRIEND];
/// @todo add a field "pcid" in the contact table that referrs to the public contact id.
$owner = DBA::selectFirst('contact', ['url', 'nurl', 'alias'], ['id' => $parent['owner-id']]);
if (!DBA::isResult($owner)) {
return;
}
$condition = ['nurl' => $owner['nurl'], 'rel' => [Contact::SHARING, Contact::FRIEND]];
$contacts = DBA::select('contact', ['uid'], $condition);
while ($contact = DBA::fetch($contacts)) {
if ($contact['uid'] == 0) {
continue;
}
$users[$contact['uid']] = $contact['uid'];
}
DBA::close($contacts);
$condition = ['alias' => $owner['url'], 'rel' => [Contact::SHARING, Contact::FRIEND]];
$contacts = DBA::select('contact', ['uid'], $condition);
while ($contact = DBA::fetch($contacts)) {
if ($contact['uid'] == 0) {
continue;
}
$users[$contact['uid']] = $contact['uid'];
}
DBA::close($contacts);
if (!empty($owner['alias'])) {
$condition = ['url' => $owner['alias'], 'rel' => [Contact::SHARING, Contact::FRIEND]];
$contacts = DBA::select('contact', ['uid'], $condition);
while ($contact = DBA::fetch($contacts)) {
if ($contact['uid'] == 0) {
continue;
}
$users[$contact['uid']] = $contact['uid'];
}
DBA::close($contacts);
}
$origin_uid = 0;
@ -2175,7 +2171,7 @@ class Item extends BaseObject
}
// is it an entry from a connector? Only add an entry for natively connected networks
if (!in_array($item["network"], [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, ""])) {
if (!in_array($item["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, ""])) {
return;
}
@ -2326,16 +2322,10 @@ class Item extends BaseObject
public static function newURI($uid, $guid = "")
{
if ($guid == "") {
$guid = System::createGUID(32);
$guid = System::createUUID();
}
$hostname = self::getApp()->get_hostname();
$user = DBA::selectFirst('user', ['nickname'], ['uid' => $uid]);
$uri = "urn:X-dfrn:" . $hostname . ':' . $user['nickname'] . ':' . $guid;
return $uri;
return self::getApp()->getBaseURL() . '/objects/' . $guid;
}
/**
@ -2618,7 +2608,7 @@ class Item extends BaseObject
}
// Prevent to forward already forwarded posts
if ($datarray["app"] == $a->get_hostname()) {
if ($datarray["app"] == $a->getHostName()) {
logger('Already forwarded (second test)', LOGGER_DEBUG);
return false;
}
@ -2659,7 +2649,7 @@ class Item extends BaseObject
}
if ($contact['network'] != Protocol::FEED) {
$datarray["guid"] = System::createGUID(32);
$datarray["guid"] = System::createUUID();
unset($datarray["plink"]);
$datarray["uri"] = self::newURI($contact['uid'], $datarray["guid"]);
$datarray["parent-uri"] = $datarray["uri"];
@ -2825,7 +2815,7 @@ class Item extends BaseObject
}
// returns an array of contact-ids that are allowed to see this object
private static function enumeratePermissions($obj)
public static function enumeratePermissions($obj)
{
$allow_people = expand_acl($obj['allow_cid']);
$allow_groups = Group::expand(expand_acl($obj['allow_gid']));
@ -3085,10 +3075,10 @@ class Item extends BaseObject
return true;
}
$objtype = $item['resource-id'] ? ACTIVITY_OBJ_IMAGE : ACTIVITY_OBJ_NOTE ;
$objtype = $item['resource-id'] ? ACTIVITY_OBJ_IMAGE : ACTIVITY_OBJ_NOTE;
$new_item = [
'guid' => System::createGUID(32),
'guid' => System::createUUID(),
'uri' => self::newURI($item['uid']),
'uid' => $item['uid'],
'contact-id' => $item_contact_id,
@ -3099,7 +3089,7 @@ class Item extends BaseObject
'parent' => $item['id'],
'parent-uri' => $item['uri'],
'thr-parent' => $item['uri'],
'owner-id' => $item['owner-id'],
'owner-id' => $author_id,
'author-id' => $author_id,
'body' => $activity,
'verb' => $activity,

View file

@ -46,7 +46,7 @@ class Mail
return -2;
}
$guid = System::createGUID(32);
$guid = System::createUUID();
$uri = 'urn:X-dfrn:' . System::baseUrl() . ':' . local_user() . ':' . $guid;
$convid = 0;
@ -73,7 +73,7 @@ class Mail
$recip_handle = (($contact['addr']) ? $contact['addr'] : $contact['nick'] . '@' . $recip_host);
$sender_handle = $a->user['nickname'] . '@' . substr(System::baseUrl(), strpos(System::baseUrl(), '://') + 3);
$conv_guid = System::createGUID(32);
$conv_guid = System::createUUID();
$convuri = $recip_handle . ':' . $conv_guid;
$handles = $recip_handle . ';' . $sender_handle;
@ -171,7 +171,7 @@ class Mail
$subject = L10n::t('[no subject]');
}
$guid = System::createGUID(32);
$guid = System::createUUID();
$uri = 'urn:X-dfrn:' . System::baseUrl() . ':' . local_user() . ':' . $guid;
$me = Probe::uri($replyto);
@ -180,7 +180,7 @@ class Mail
return -2;
}
$conv_guid = System::createGUID(32);
$conv_guid = System::createUUID();
$recip_handle = $recipient['nickname'] . '@' . substr(System::baseUrl(), strpos(System::baseUrl(), '://') + 3);

View file

@ -146,7 +146,7 @@ class Photo
// Remove the cached photo
$a = get_app();
$basepath = $a->get_basepath();
$basepath = $a->getBasePath();
if (is_dir($basepath . "/photo")) {
$filename = $basepath . '/photo/' . $hash . '-4.' . $Image->getExt();

View file

@ -28,6 +28,19 @@ require_once 'include/dba.php';
class Profile
{
/**
* @brief Returns default profile for a given user id
*
* @param integer User ID
*
* @return array Profile data
*/
public static function getByUID($uid)
{
$profile = DBA::selectFirst('profile', [], ['uid' => $uid, 'is-default' => true]);
return $profile;
}
/**
* @brief Returns a formatted location string from the given profile array
*
@ -149,7 +162,7 @@ class Profile
* load/reload current theme info
*/
$a->set_template_engine(); // reset the template engine to the default in case the user's theme doesn't specify one
$a->setActiveTemplateEngine(); // reset the template engine to the default in case the user's theme doesn't specify one
$theme_info_file = 'view/theme/' . $a->getCurrentTheme() . '/theme.php';
if (file_exists($theme_info_file)) {
@ -361,7 +374,7 @@ class Profile
if ($r) {
$remote_url = $r[0]['url'];
$message_path = preg_replace('=(.*)/profile/(.*)=ism', '$1/message/new/', $remote_url);
$wallmessage_link = $message_path . base64_encode($profile['addr']);
$wallmessage_link = $message_path . base64_encode(defaults($profile, 'addr', ''));
} else if (!empty($profile['nickname'])) {
$wallmessage_link = 'wallmessage/' . $profile['nickname'];
}
@ -492,7 +505,7 @@ class Profile
if (isset($p['address'])) {
$p['address'] = BBCode::convert($p['address']);
} else {
} elseif (isset($p['location'])) {
$p['address'] = BBCode::convert($p['location']);
}
@ -997,56 +1010,59 @@ class Profile
$my_url = self::getMyURL();
$my_url = Network::isUrlValid($my_url);
if ($my_url) {
if (!local_user()) {
// Is it a DDoS attempt?
// The check fetches the cached value from gprobe to reduce the load for this system
$urlparts = parse_url($my_url);
if (empty($my_url) || local_user()) {
return;
}
$result = Cache::get('gprobe:' . $urlparts['host']);
if ((!is_null($result)) && (in_array($result['network'], [Protocol::FEED, Protocol::PHANTOM]))) {
logger('DDoS attempt detected for ' . $urlparts['host'] . ' by ' . $_SERVER['REMOTE_ADDR'] . '. server data: ' . print_r($_SERVER, true), LOGGER_DEBUG);
return;
}
$arr = ['zrl' => $my_url, 'url' => $a->cmd];
Addon::callHooks('zrl_init', $arr);
Worker::add(PRIORITY_LOW, 'GProbe', $my_url);
$arr = ['zrl' => $my_url, 'url' => $a->cmd];
Addon::callHooks('zrl_init', $arr);
// Try to find the public contact entry of the visitor.
$cid = Contact::getIdForURL($my_url);
if (!$cid) {
logger('No contact record found for ' . $my_url, LOGGER_DEBUG);
return;
}
// Try to find the public contact entry of the visitor.
$cid = Contact::getIdForURL($my_url);
if (!$cid) {
logger('No contact record found for ' . $my_url, LOGGER_DEBUG);
return;
}
$contact = DBA::selectFirst('contact',['id', 'url'], ['id' => $cid]);
$contact = DBA::selectFirst('contact',['id', 'url'], ['id' => $cid]);
if (DBA::isResult($contact) && remote_user() && remote_user() == $contact['id']) {
logger('The visitor ' . $my_url . ' is already authenticated', LOGGER_DEBUG);
return;
}
if (DBA::isResult($contact) && remote_user() && remote_user() == $contact['id']) {
// The visitor is already authenticated.
return;
}
// Avoid endless loops
$cachekey = 'zrlInit:' . $my_url;
if (Cache::get($cachekey)) {
logger('URL ' . $my_url . ' already tried to authenticate.', LOGGER_DEBUG);
return;
} else {
Cache::set($cachekey, true, CACHE_MINUTE);
}
logger('Not authenticated. Invoking reverse magic-auth for ' . $my_url, LOGGER_DEBUG);
logger('Not authenticated. Invoking reverse magic-auth for ' . $my_url, LOGGER_DEBUG);
// Try to avoid recursion - but send them home to do a proper magic auth.
$query = str_replace(array('?zrl=', '&zid='), array('?rzrl=', '&rzrl='), $a->query_string);
// The other instance needs to know where to redirect.
$dest = urlencode(System::baseUrl() . '/' . $query);
Worker::add(PRIORITY_LOW, 'GProbe', $my_url);
// We need to extract the basebath from the profile url
// to redirect the visitors '/magic' module.
// Note: We should have the basepath of a contact also in the contact table.
$urlarr = explode('/profile/', $contact['url']);
$basepath = $urlarr[0];
// Try to avoid recursion - but send them home to do a proper magic auth.
$query = str_replace(array('?zrl=', '&zid='), array('?rzrl=', '&rzrl='), $a->query_string);
// The other instance needs to know where to redirect.
$dest = urlencode(System::baseUrl() . '/' . $query);
if ($basepath != System::baseUrl() && !strstr($dest, '/magic') && !strstr($dest, '/rmagic')) {
$magic_path = $basepath . '/magic' . '?f=&owa=1&dest=' . $dest;
$serverret = Network::curl($magic_path);
if (!empty($serverret['success'])) {
goaway($magic_path);
}
}
// We need to extract the basebath from the profile url
// to redirect the visitors '/magic' module.
// Note: We should have the basepath of a contact also in the contact table.
$urlarr = explode('/profile/', $contact['url']);
$basepath = $urlarr[0];
if ($basepath != System::baseUrl() && !strstr($dest, '/magic') && !strstr($dest, '/rmagic')) {
$magic_path = $basepath . '/magic' . '?f=&owa=1&dest=' . $dest;
// We have to check if the remote server does understand /magic without invoking something
$serverret = Network::curl($basepath . '/magic');
if ($serverret->isSuccess()) {
logger('Doing magic auth for visitor ' . $my_url . ' to ' . $magic_path, LOGGER_DEBUG);
goaway($magic_path);
}
}
}
@ -1103,7 +1119,7 @@ class Profile
$a->contact = $arr['visitor'];
info(L10n::t('OpenWebAuth: %1$s welcomes %2$s', $a->get_hostname(), $visitor['name']));
info(L10n::t('OpenWebAuth: %1$s welcomes %2$s', $a->getHostName(), $visitor['name']));
logger('OpenWebAuth: auth success from ' . $visitor['addr'], LOGGER_DEBUG);
}

131
src/Model/Register.php Normal file
View file

@ -0,0 +1,131 @@
<?php
/**
* @file src/Model/Register.php
*/
namespace Friendica\Model;
use Friendica\Database\DBA;
use Friendica\Util\DateTimeFormat;
/**
* Class interacting with the register database table
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
*/
class Register
{
/**
* Return the list of pending registrations
*
* @return array
*/
public static function getPending()
{
$stmt = DBA::p(
"SELECT `register`.*, `contact`.`name`, `user`.`email`
FROM `register`
INNER JOIN `contact` ON `register`.`uid` = `contact`.`uid`
INNER JOIN `user` ON `register`.`uid` = `user`.`uid`"
);
return DBA::toArray($stmt);
}
/**
* Returns the pending registration count
*
* @return int
*/
public static function getPendingCount()
{
$register = DBA::fetchFirst(
"SELECT COUNT(*) AS `count`
FROM `register`
INNER JOIN `contact` ON `register`.`uid` = `contact`.`uid` AND `contact`.`self`"
);
return $register['count'];
}
/**
* Returns the register record associated with the provided hash
*
* @param string $hash
* @return array
*/
public static function getByHash($hash)
{
return DBA::selectFirst('register', [], ['hash' => $hash]);
}
/**
* Returns true if a register record exists with the provided hash
*
* @param string $hash
* @return boolean
*/
public static function existsByHash($hash)
{
return DBA::exists('register', ['hash' => $hash]);
}
/**
* Creates a register record for an invitation and returns the auto-generated code for it
*
* @return string
*/
public static function createForInvitation()
{
$code = autoname(8) . srand(1000, 9999);
$fields = [
'hash' => $code,
'created' => DateTimeFormat::utcNow()
];
DBA::insert('register', $fields);
return $code;
}
/**
* Creates a register record for approval and returns the success of the database insert
* Checks for the existence of the provided user id
*
* @param integer $uid The ID of the user needing approval
* @param string $language The registration language
* @param string $note An additional message from the user
* @return boolean
*/
public static function createForApproval($uid, $language, $note = '')
{
$hash = random_string();
if (!User::exists($uid)) {
return false;
}
$fields = [
'hash' => $hash,
'created' => DateTimeFormat::utcNow(),
'uid' => $uid,
'password' => '', // Obsolete, slated for deletion
'language' => $language,
'note' => $note
];
return DBA::insert('register', $fields);
}
/**
* Deletes a register record by the provided hash and returns the success of the database deletion
*
* @param string $hash
* @return boolean
*/
public static function deleteByHash($hash)
{
return DBA::delete('register', ['hash' => $hash]);
}
}

View file

@ -33,6 +33,17 @@ class Term
return $tag_text;
}
public static function tagArrayFromItemId($itemid, $type = [TERM_HASHTAG, TERM_MENTION])
{
$condition = ['otype' => TERM_OBJ_POST, 'oid' => $itemid, 'type' => $type];
$tags = DBA::select('term', ['type', 'term', 'url'], $condition);
if (!DBA::isResult($tags)) {
return [];
}
return DBA::toArray($tags);
}
public static function fileTextFromItemId($itemid)
{
$file_text = '';
@ -99,6 +110,18 @@ class Term
$pattern = '/\W([\#@])\[url\=(.*?)\](.*?)\[\/url\]/ism';
if (preg_match_all($pattern, $data, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
if ($match[1] == '@') {
$contact = Contact::getDetailsByURL($match[2], 0);
if (!empty($contact['addr'])) {
$match[3] = $contact['addr'];
}
if (!empty($contact['url'])) {
$match[2] = $contact['url'];
}
}
$tags[$match[1] . trim($match[3], ',.:;[]/\"?!')] = $match[2];
}
}
@ -119,12 +142,22 @@ class Term
$term = substr($tag, 1);
} elseif (substr(trim($tag), 0, 1) == '@') {
$type = TERM_MENTION;
$term = substr($tag, 1);
$contact = Contact::getDetailsByURL($link, 0);
if (!empty($contact['name'])) {
$term = $contact['name'];
} else {
$term = substr($tag, 1);
}
} else { // This shouldn't happen
$type = TERM_HASHTAG;
$term = $tag;
}
if (DBA::exists('term', ['uid' => $message['uid'], 'otype' => TERM_OBJ_POST, 'oid' => $itemid, 'url' => $link])) {
continue;
}
if ($message['uid'] == 0) {
$global = true;
DBA::update('term', ['global' => true], ['otype' => TERM_OBJ_POST, 'guid' => $message['guid']]);

View file

@ -31,6 +31,43 @@ require_once 'include/text.php';
*/
class User
{
/**
* Returns true if a user record exists with the provided id
*
* @param integer $uid
* @return boolean
*/
public static function exists($uid)
{
return DBA::exists('user', ['uid' => $uid]);
}
/**
* @param integer $uid
* @return array|boolean User record if it exists, false otherwise
*/
public static function getById($uid)
{
return DBA::selectFirst('user', [], ['uid' => $uid]);
}
/**
* @brief Returns the user id of a given profile URL
*
* @param string $url
*
* @return integer user id
*/
public static function getIdForURL($url)
{
$self = DBA::selectFirst('contact', ['uid'], ['nurl' => normalise_link($url), 'self' => true]);
if (!DBA::isResult($self)) {
return false;
} else {
return $self['uid'];
}
}
/**
* @brief Get owner data by user id
*
@ -384,7 +421,7 @@ class User
throw new Exception(L10n::t('An invitation is required.'));
}
if (!DBA::exists('register', ['hash' => $invite_id])) {
if (!Register::existsByHash($invite_id)) {
throw new Exception(L10n::t('Invitation could not be verified.'));
}
}
@ -397,7 +434,7 @@ class User
$_SESSION['register'] = 1;
$_SESSION['openid'] = $openid_url;
$openid = new LightOpenID($a->get_hostname());
$openid = new LightOpenID($a->getHostName());
$openid->identity = $openid_url;
$openid->returnUrl = System::baseUrl() . '/openid';
$openid->required = ['namePerson/friendly', 'contact/email', 'namePerson'];
@ -495,7 +532,7 @@ class User
$spubkey = $sres['pubkey'];
$insert_result = DBA::insert('user', [
'guid' => System::createGUID(32),
'guid' => System::createUUID(),
'username' => $username,
'password' => $new_password_encoded,
'email' => $email,
@ -627,27 +664,36 @@ class User
}
/**
* @brief Sends pending registration confiŕmation email
* @brief Sends pending registration confirmation email
*
* @param string $email
* @param array $user User record array
* @param string $sitename
* @param string $username
* @param string $siteurl
* @param string $password Plaintext password
* @return NULL|boolean from notification() and email() inherited
*/
public static function sendRegisterPendingEmail($email, $sitename, $username)
public static function sendRegisterPendingEmail($user, $sitename, $siteurl, $password)
{
$body = deindent(L10n::t('
Dear %1$s,
Thank you for registering at %2$s. Your account is pending for approval by the administrator.
'));
$body = sprintf($body, $username, $sitename);
Your login details are as follows:
Site Location: %3$s
Login Name: %4$s
Password: %5$s
',
$body, $user['username'], $sitename, $siteurl, $user['nickname'], $password
));
return notification([
'type' => SYSTEM_EMAIL,
'to_email' => $email,
'subject'=> L10n::t('Registration at %s', $sitename),
'body' => $body]);
'type' => SYSTEM_EMAIL,
'uid' => $user['uid'],
'to_email' => $user['email'],
'subject' => L10n::t('Registration at %s', $sitename),
'body' => $body
]);
}
/**
@ -655,19 +701,20 @@ class User
*
* It's here as a function because the mail is sent from different parts
*
* @param string $email
* @param array $user User record array
* @param string $sitename
* @param string $siteurl
* @param string $username
* @param string $password
* @param string $password Plaintext password
* @return NULL|boolean from notification() and email() inherited
*/
public static function sendRegisterOpenEmail($email, $sitename, $siteurl, $username, $password, $user)
public static function sendRegisterOpenEmail($user, $sitename, $siteurl, $password)
{
$preamble = deindent(L10n::t('
Dear %1$s,
Thank you for registering at %2$s. Your account has been created.
'));
',
$preamble, $user['username'], $sitename
));
$body = deindent(L10n::t('
The login details are as follows:
@ -694,19 +741,19 @@ class User
If you ever want to delete your account, you can do so at %3$s/removeme
Thank you and welcome to %2$s.'));
$preamble = sprintf($preamble, $username, $sitename);
$body = sprintf($body, $email, $sitename, $siteurl, $username, $password);
Thank you and welcome to %2$s.',
$body, $user['email'], $sitename, $siteurl, $user['username'], $password
));
return notification([
'uid' => $user['uid'],
'uid' => $user['uid'],
'language' => $user['language'],
'type' => SYSTEM_EMAIL,
'to_email' => $email,
'subject'=> L10n::t('Registration details for %s', $sitename),
'preamble'=> $preamble,
'body' => $body]);
'type' => SYSTEM_EMAIL,
'to_email' => $user['email'],
'subject' => L10n::t('Registration details for %s', $sitename),
'preamble' => $preamble,
'body' => $body
]);
}
/**
@ -730,7 +777,7 @@ class User
DBA::insert('userd', ['username' => $user['nickname']]);
// The user and related data will be deleted in "cron_expire_and_remove_users" (cronjobs.php)
DBA::update('user', ['account_removed' => true, 'account_expires_on' => DateTimeFormat::utc($t . " + 7 day")], ['uid' => $uid]);
DBA::update('user', ['account_removed' => true, 'account_expires_on' => DateTimeFormat::utc(DateTimeFormat::utcNow() . " + 7 day")], ['uid' => $uid]);
Worker::add(PRIORITY_HIGH, "Notifier", "removeme", $uid);
// Send an update to the directory
@ -743,7 +790,7 @@ class User
if ($uid == local_user()) {
unset($_SESSION['authenticated']);
unset($_SESSION['uid']);
goaway(System::baseUrl());
goaway();;
}
}
}

26
src/Module/Acctlink.php Normal file
View file

@ -0,0 +1,26 @@
<?php
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Network\Probe;
/**
* Redirects to another URL based on the parameter 'addr'
*/
class Acctlink extends BaseModule
{
public static function content()
{
$addr = defaults($_GET, 'addr', false);
if ($addr) {
$url = defaults(Probe::uri(trim($addr)), 'url', false);
if ($url) {
goaway($url);
exit();
}
}
}
}

1106
src/Module/Contact.php Normal file
View file

@ -0,0 +1,1106 @@
<?php
namespace Friendica\Module;
use Friendica\App;
use Friendica\BaseModule;
use Friendica\Content\ContactSelector;
use Friendica\Content\Nav;
use Friendica\Content\Text\BBCode;
use Friendica\Content\Widget;
use Friendica\Core\Addon;
use Friendica\Core\L10n;
use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\Model;
use Friendica\Network\Probe;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Proxy as ProxyUtils;
use Friendica\Core\ACL;
use Friendica\Module\Login;
/**
* Manages and show Contacts and their content
*
* @brief manages contacts
*/
class Contact extends BaseModule
{
public static function init()
{
$a = self::getApp();
if (!local_user()) {
return;
}
$nets = defaults($_GET, 'nets', '');
if ($nets == 'all') {
$nets = '';
}
if (!x($a->page, 'aside')) {
$a->page['aside'] = '';
}
$contact_id = null;
$contact = null;
if ($a->argc == 2 && intval($a->argv[1])
|| $a->argc == 3 && intval($a->argv[1]) && in_array($a->argv[2], ['posts', 'conversations'])
) {
$contact_id = intval($a->argv[1]);
$contact = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => local_user()]);
if (!DBA::isResult($contact)) {
$contact = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => 0]);
}
// Don't display contacts that are about to be deleted
if ($contact['network'] == Protocol::PHANTOM) {
$contact = false;
}
}
if (DBA::isResult($contact)) {
if ($contact['self']) {
if (($a->argc == 3) && intval($a->argv[1]) && in_array($a->argv[2], ['posts', 'conversations'])) {
goaway('profile/' . $contact['nick']);
} else {
goaway('profile/' . $contact['nick'] . '?tab=profile');
}
}
$a->data['contact'] = $contact;
if (($contact['network'] != '') && ($contact['network'] != Protocol::DFRN)) {
$networkname = format_network_name($contact['network'], $contact['url']);
} else {
$networkname = '';
}
/// @TODO Add nice spaces
$vcard_widget = replace_macros(get_markup_template('vcard-widget.tpl'), [
'$name' => htmlentities($contact['name']),
'$photo' => $contact['photo'],
'$url' => Model\Contact::MagicLink($contact['url']),
'$addr' => defaults($contact, 'addr', ''),
'$network_name' => $networkname,
'$network' => L10n::t('Network:'),
'$account_type' => Model\Contact::getAccountType($contact)
]);
$findpeople_widget = '';
$follow_widget = '';
$networks_widget = '';
} else {
$vcard_widget = '';
$networks_widget = Widget::networks('contact', $nets);
if (isset($_GET['add'])) {
$follow_widget = Widget::follow($_GET['add']);
} else {
$follow_widget = Widget::follow();
}
$findpeople_widget = Widget::findPeople();
}
if ($contact['uid'] != 0) {
$groups_widget = Model\Group::sidebarWidget('contact', 'group', 'full', 'everyone', $contact_id);
} else {
$groups_widget = null;
}
$a->page['aside'] .= replace_macros(get_markup_template('contacts-widget-sidebar.tpl'), [
'$vcard_widget' => $vcard_widget,
'$findpeople_widget' => $findpeople_widget,
'$follow_widget' => $follow_widget,
'$groups_widget' => $groups_widget,
'$networks_widget' => $networks_widget
]);
$base = $a->getBaseURL();
$tpl = get_markup_template('contacts-head.tpl');
$a->page['htmlhead'] .= replace_macros($tpl, [
'$baseurl' => System::baseUrl(true),
'$base' => $base
]);
}
private static function batchActions(App $a)
{
if (empty($_POST['contact_batch']) || !is_array($_POST['contact_batch'])) {
return;
}
$contacts_id = $_POST['contact_batch'];
$stmt = DBA::select('contact', ['id'], ['id' => $contacts_id, 'uid' => local_user(), 'self' => false]);
$orig_records = DBA::toArray($stmt);
$count_actions = 0;
foreach ($orig_records as $orig_record) {
$contact_id = $orig_record['id'];
if (!empty($_POST['contacts_batch_update'])) {
self::updateContactFromPoll($contact_id);
$count_actions++;
}
if (!empty($_POST['contacts_batch_block'])) {
self::blockContact($contact_id);
$count_actions++;
}
if (!empty($_POST['contacts_batch_ignore'])) {
self::ignoreContact($contact_id);
$count_actions++;
}
if (!empty($_POST['contacts_batch_archive'])
&& self::archiveContact($contact_id, $orig_record)
) {
$count_actions++;
}
if (!empty($_POST['contacts_batch_drop'])) {
self::dropContact($orig_record);
$count_actions++;
}
}
if ($count_actions > 0) {
info(L10n::tt('%d contact edited.', '%d contacts edited.', $count_actions));
}
goaway('contact');
}
public static function post()
{
$a = self::getApp();
if (!local_user()) {
return;
}
if ($a->argv[1] === 'batch') {
self::batchActions($a);
return;
}
$contact_id = intval($a->argv[1]);
if (!$contact_id) {
return;
}
if (!DBA::exists('contact', ['id' => $contact_id, 'uid' => local_user()])) {
notice(L10n::t('Could not access contact record.') . EOL);
goaway('contact');
return; // NOTREACHED
}
Addon::callHooks('contact_edit_post', $_POST);
$profile_id = intval(defaults($_POST, 'profile-assign', 0));
if ($profile_id) {
if (!DBA::exists('profile', ['id' => $profile_id, 'uid' => local_user()])) {
notice(L10n::t('Could not locate selected profile.') . EOL);
return;
}
}
$hidden = !empty($_POST['hidden']);
$notify = !empty($_POST['notify']);
$fetch_further_information = intval(defaults($_POST, 'fetch_further_information', 0));
$ffi_keyword_blacklist = escape_tags(trim(defaults($_POST, 'ffi_keyword_blacklist', '')));
$priority = intval(defaults($_POST, 'poll', 0));
if ($priority > 5 || $priority < 0) {
$priority = 0;
}
$info = escape_tags(trim($_POST['info']));
$r = DBA::update('contact', [
'profile-id' => $profile_id,
'priority' => $priority,
'info' => $info,
'hidden' => $hidden,
'notify_new_posts' => $notify,
'fetch_further_information' => $fetch_further_information,
'ffi_keyword_blacklist' => $ffi_keyword_blacklist],
['id' => $contact_id, 'uid' => local_user()]
);
if (DBA::isResult($r)) {
info(L10n::t('Contact updated.') . EOL);
} else {
notice(L10n::t('Failed to update contact record.') . EOL);
}
$contact = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => local_user()]);
if (DBA::isResult($contact)) {
$a->data['contact'] = $contact;
}
return;
}
/* contact actions */
private static function updateContactFromPoll($contact_id)
{
$contact = DBA::selectFirst('contact', ['uid', 'url', 'network'], ['id' => $contact_id, 'uid' => local_user()]);
if (!DBA::isResult($contact)) {
return;
}
$uid = $contact['uid'];
if ($contact['network'] == Protocol::OSTATUS) {
$result = Model\Contact::createFromProbe($uid, $contact['url'], false, $contact['network']);
if ($result['success']) {
DBA::update('contact', ['subhub' => 1], ['id' => $contact_id]);
}
} else {
// pull feed and consume it, which should subscribe to the hub.
Worker::add(PRIORITY_HIGH, 'OnePoll', $contact_id, 'force');
}
}
private static function updateContactFromProbe($contact_id)
{
$contact = DBA::selectFirst('contact', ['uid', 'url', 'network'], ['id' => $contact_id, 'uid' => local_user()]);
if (!DBA::isResult($contact)) {
return;
}
$uid = $contact['uid'];
$data = Probe::uri($contact['url'], '', 0, false);
// 'Feed' or 'Unknown' is mostly a sign of communication problems
if ((in_array($data['network'], [Protocol::FEED, Protocol::PHANTOM])) && ($data['network'] != $contact['network'])) {
return;
}
$updatefields = ['name', 'nick', 'url', 'addr', 'batch', 'notify', 'poll', 'request', 'confirm', 'poco', 'network', 'alias'];
$fields = [];
if ($data['network'] == Protocol::OSTATUS) {
$result = Model\Contact::createFromProbe($uid, $data['url'], false);
if ($result['success']) {
$fields['subhub'] = true;
}
}
foreach ($updatefields AS $field) {
if (!empty($data[$field])) {
$fields[$field] = $data[$field];
}
}
$fields['nurl'] = normalise_link($data['url']);
if (!empty($data['priority'])) {
$fields['priority'] = intval($data['priority']);
}
if (empty($fields)) {
return;
}
$r = DBA::update('contact', $fields, ['id' => $contact_id, 'uid' => local_user()]);
// Update the entry in the contact table
Model\Contact::updateAvatar($data['photo'], local_user(), $contact_id, true);
// Update the entry in the gcontact table
Model\GContact::updateFromProbe($data['url']);
}
private static function blockContact($contact_id)
{
$blocked = !Model\Contact::isBlockedByUser($contact_id, local_user());
Model\Contact::setBlockedForUser($contact_id, local_user(), $blocked);
}
private static function ignoreContact($contact_id)
{
$ignored = !Model\Contact::isIgnoredByUser($contact_id, local_user());
Model\Contact::setIgnoredForUser($contact_id, local_user(), $ignored);
}
private static function archiveContact($contact_id, $orig_record)
{
$archived = (($orig_record['archive']) ? 0 : 1);
$r = DBA::update('contact', ['archive' => $archived], ['id' => $contact_id, 'uid' => local_user()]);
return DBA::isResult($r);
}
private static function dropContact($orig_record)
{
$owner = Model\User::getOwnerDataById(local_user());
if (!DBA::isResult($owner)) {
return;
}
Model\Contact::terminateFriendship($owner, $orig_record, true);
Model\Contact::remove($orig_record['id']);
}
public static function content($update = 0)
{
$a = self::getApp();
$sort_type = 0;
$o = '';
Nav::setSelected('contact');
if (!local_user()) {
notice(L10n::t('Permission denied.') . EOL);
return Login::form();
}
if ($a->argc == 3) {
$contact_id = intval($a->argv[1]);
if (!$contact_id) {
return;
}
$cmd = $a->argv[2];
$orig_record = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => [0, local_user()], 'self' => false]);
if (!DBA::isResult($orig_record)) {
notice(L10n::t('Could not access contact record.') . EOL);
goaway('contact');
return; // NOTREACHED
}
if ($cmd === 'update' && ($orig_record['uid'] != 0)) {
self::updateContactFromPoll($contact_id);
goaway('contact/' . $contact_id);
// NOTREACHED
}
if ($cmd === 'updateprofile' && ($orig_record['uid'] != 0)) {
self::updateContactFromProbe($contact_id);
goaway('crepair/' . $contact_id);
// NOTREACHED
}
if ($cmd === 'block') {
self::blockContact($contact_id);
$blocked = Model\Contact::isBlockedByUser($contact_id, local_user());
info(($blocked ? L10n::t('Contact has been blocked') : L10n::t('Contact has been unblocked')) . EOL);
goaway('contact/' . $contact_id);
return; // NOTREACHED
}
if ($cmd === 'ignore') {
self::ignoreContact($contact_id);
$ignored = Model\Contact::isIgnoredByUser($contact_id, local_user());
info(($ignored ? L10n::t('Contact has been ignored') : L10n::t('Contact has been unignored')) . EOL);
goaway('contact/' . $contact_id);
return; // NOTREACHED
}
if ($cmd === 'archive' && ($orig_record['uid'] != 0)) {
$r = self::archiveContact($contact_id, $orig_record);
if ($r) {
$archived = (($orig_record['archive']) ? 0 : 1);
info((($archived) ? L10n::t('Contact has been archived') : L10n::t('Contact has been unarchived')) . EOL);
}
goaway('contact/' . $contact_id);
return; // NOTREACHED
}
if ($cmd === 'drop' && ($orig_record['uid'] != 0)) {
// Check if we should do HTML-based delete confirmation
if (!empty($_REQUEST['confirm'])) {
// <form> can't take arguments in its 'action' parameter
// so add any arguments as hidden inputs
$query = explode_querystring($a->query_string);
$inputs = [];
foreach ($query['args'] as $arg) {
if (strpos($arg, 'confirm=') === false) {
$arg_parts = explode('=', $arg);
$inputs[] = ['name' => $arg_parts[0], 'value' => $arg_parts[1]];
}
}
$a->page['aside'] = '';
return replace_macros(get_markup_template('contact_drop_confirm.tpl'), [
'$header' => L10n::t('Drop contact'),
'$contact' => self::getContactTemplateVars($orig_record),
'$method' => 'get',
'$message' => L10n::t('Do you really want to delete this contact?'),
'$extra_inputs' => $inputs,
'$confirm' => L10n::t('Yes'),
'$confirm_url' => $query['base'],
'$confirm_name' => 'confirmed',
'$cancel' => L10n::t('Cancel'),
]);
}
// Now check how the user responded to the confirmation query
if (!empty($_REQUEST['canceled'])) {
goaway('contact');
}
self::dropContact($orig_record);
info(L10n::t('Contact has been removed.') . EOL);
goaway('contact');
return; // NOTREACHED
}
if ($cmd === 'posts') {
return self::getPostsHTML($a, $contact_id);
}
if ($cmd === 'conversations') {
return self::getConversationsHMTL($a, $contact_id, $update);
}
}
$_SESSION['return_url'] = $a->query_string;
if (!empty($a->data['contact']) && is_array($a->data['contact'])) {
$contact_id = $a->data['contact']['id'];
$contact = $a->data['contact'];
$a->page['htmlhead'] .= replace_macros(get_markup_template('contact_head.tpl'), [
'$baseurl' => $a->getBaseURL(true),
]);
$contact['blocked'] = Model\Contact::isBlockedByUser($contact['id'], local_user());
$contact['readonly'] = Model\Contact::isIgnoredByUser($contact['id'], local_user());
$dir_icon = '';
$relation_text = '';
switch ($contact['rel']) {
case Model\Contact::FRIEND:
$dir_icon = 'images/lrarrow.gif';
$relation_text = L10n::t('You are mutual friends with %s');
break;
case Model\Contact::FOLLOWER;
$dir_icon = 'images/larrow.gif';
$relation_text = L10n::t('You are sharing with %s');
break;
case Model\Contact::SHARING;
$dir_icon = 'images/rarrow.gif';
$relation_text = L10n::t('%s is sharing with you');
break;
default:
break;
}
if ($contact['uid'] == 0) {
$relation_text = '';
}
if (!in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA])) {
$relation_text = '';
}
$relation_text = sprintf($relation_text, htmlentities($contact['name']));
$url = Model\Contact::magicLink($contact['url']);
if (strpos($url, 'redir/') === 0) {
$sparkle = ' class="sparkle" ';
} else {
$sparkle = '';
}
$insecure = L10n::t('Private communications are not available for this contact.');
$last_update = (($contact['last-update'] <= NULL_DATE) ? L10n::t('Never') : DateTimeFormat::local($contact['last-update'], 'D, j M Y, g:i A'));
if ($contact['last-update'] > NULL_DATE) {
$last_update .= ' ' . (($contact['last-update'] <= $contact['success_update']) ? L10n::t('(Update was successful)') : L10n::t('(Update was not successful)'));
}
$lblsuggest = (($contact['network'] === Protocol::DFRN) ? L10n::t('Suggest friends') : '');
$poll_enabled = in_array($contact['network'], [Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
$nettype = L10n::t('Network type: %s', ContactSelector::networkToName($contact['network'], $contact['url']));
// tabs
$tab_str = self::getTabsHTML($a, $contact, 3);
$lost_contact = (($contact['archive'] && $contact['term-date'] > NULL_DATE && $contact['term-date'] < DateTimeFormat::utcNow()) ? L10n::t('Communications lost with this contact!') : '');
$fetch_further_information = null;
if ($contact['network'] == Protocol::FEED) {
$fetch_further_information = [
'fetch_further_information',
L10n::t('Fetch further information for feeds'),
$contact['fetch_further_information'],
L10n::t('Fetch information like preview pictures, title and teaser from the feed item. You can activate this if the feed doesn\'t contain much text. Keywords are taken from the meta header in the feed item and are posted as hash tags.'),
[
'0' => L10n::t('Disabled'),
'1' => L10n::t('Fetch information'),
'3' => L10n::t('Fetch keywords'),
'2' => L10n::t('Fetch information and keywords')
]
];
}
$poll_interval = null;
if (in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) {
$poll_interval = ContactSelector::pollInterval($contact['priority'], !$poll_enabled);
}
$profile_select = null;
if ($contact['network'] == Protocol::DFRN) {
$profile_select = ContactSelector::profileAssign($contact['profile-id'], $contact['network'] !== Protocol::DFRN);
}
/// @todo Only show the following link with DFRN when the remote version supports it
$follow = '';
$follow_text = '';
if (in_array($contact['rel'], [Model\Contact::FRIEND, Model\Contact::SHARING])) {
if (in_array($contact['network'], Protocol::NATIVE_SUPPORT)) {
$follow = $a->getBaseURL(true) . '/unfollow?url=' . urlencode($contact['url']);
$follow_text = L10n::t('Disconnect/Unfollow');
}
} else {
$follow = $a->getBaseURL(true) . '/follow?url=' . urlencode($contact['url']);
$follow_text = L10n::t('Connect/Follow');
}
// Load contactact related actions like hide, suggest, delete and others
$contact_actions = self::getContactActions($contact);
if ($contact['uid'] != 0) {
$lbl_vis1 = L10n::t('Profile Visibility');
$lbl_info1 = L10n::t('Contact Information / Notes');
$contact_settings_label = L10n::t('Contact Settings');
} else {
$lbl_vis1 = null;
$lbl_info1 = null;
$contact_settings_label = null;
}
$tpl = get_markup_template('contact_edit.tpl');
$o .= replace_macros($tpl, [
'$header' => L10n::t('Contact'),
'$tab_str' => $tab_str,
'$submit' => L10n::t('Submit'),
'$lbl_vis1' => $lbl_vis1,
'$lbl_vis2' => L10n::t('Please choose the profile you would like to display to %s when viewing your profile securely.', $contact['name']),
'$lbl_info1' => $lbl_info1,
'$lbl_info2' => L10n::t('Their personal note'),
'$reason' => trim(notags($contact['reason'])),
'$infedit' => L10n::t('Edit contact notes'),
'$common_link' => 'common/loc/' . local_user() . '/' . $contact['id'],
'$relation_text' => $relation_text,
'$visit' => L10n::t('Visit %s\'s profile [%s]', $contact['name'], $contact['url']),
'$blockunblock' => L10n::t('Block/Unblock contact'),
'$ignorecont' => L10n::t('Ignore contact'),
'$lblcrepair' => L10n::t('Repair URL settings'),
'$lblrecent' => L10n::t('View conversations'),
'$lblsuggest' => $lblsuggest,
'$nettype' => $nettype,
'$poll_interval' => $poll_interval,
'$poll_enabled' => $poll_enabled,
'$lastupdtext' => L10n::t('Last update:'),
'$lost_contact' => $lost_contact,
'$updpub' => L10n::t('Update public posts'),
'$last_update' => $last_update,
'$udnow' => L10n::t('Update now'),
'$follow' => $follow,
'$follow_text' => $follow_text,
'$profile_select' => $profile_select,
'$contact_id' => $contact['id'],
'$block_text' => ($contact['blocked'] ? L10n::t('Unblock') : L10n::t('Block')),
'$ignore_text' => ($contact['readonly'] ? L10n::t('Unignore') : L10n::t('Ignore')),
'$insecure' => (in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::MAIL, Protocol::DIASPORA]) ? '' : $insecure),
'$info' => $contact['info'],
'$cinfo' => ['info', '', $contact['info'], ''],
'$blocked' => ($contact['blocked'] ? L10n::t('Currently blocked') : ''),
'$ignored' => ($contact['readonly'] ? L10n::t('Currently ignored') : ''),
'$archived' => ($contact['archive'] ? L10n::t('Currently archived') : ''),
'$pending' => ($contact['pending'] ? L10n::t('Awaiting connection acknowledge') : ''),
'$hidden' => ['hidden', L10n::t('Hide this contact from others'), ($contact['hidden'] == 1), L10n::t('Replies/likes to your public posts <strong>may</strong> still be visible')],
'$notify' => ['notify', L10n::t('Notification for new posts'), ($contact['notify_new_posts'] == 1), L10n::t('Send a notification of every new post of this contact')],
'$fetch_further_information' => $fetch_further_information,
'$ffi_keyword_blacklist' => $contact['ffi_keyword_blacklist'],
'$ffi_keyword_blacklist' => ['ffi_keyword_blacklist', L10n::t('Blacklisted keywords'), $contact['ffi_keyword_blacklist'], L10n::t('Comma separated list of keywords that should not be converted to hashtags, when "Fetch information and keywords" is selected')],
'$photo' => $contact['photo'],
'$name' => htmlentities($contact['name']),
'$dir_icon' => $dir_icon,
'$sparkle' => $sparkle,
'$url' => $url,
'$profileurllabel'=> L10n::t('Profile URL'),
'$profileurl' => $contact['url'],
'$account_type' => Model\Contact::getAccountType($contact),
'$location' => BBCode::convert($contact['location']),
'$location_label' => L10n::t('Location:'),
'$xmpp' => BBCode::convert($contact['xmpp']),
'$xmpp_label' => L10n::t('XMPP:'),
'$about' => BBCode::convert($contact['about'], false),
'$about_label' => L10n::t('About:'),
'$keywords' => $contact['keywords'],
'$keywords_label' => L10n::t('Tags:'),
'$contact_action_button' => L10n::t('Actions'),
'$contact_actions'=> $contact_actions,
'$contact_status' => L10n::t('Status'),
'$contact_settings_label' => $contact_settings_label,
'$contact_profile_label' => L10n::t('Profile'),
]);
$arr = ['contact' => $contact, 'output' => $o];
Addon::callHooks('contact_edit', $arr);
return $arr['output'];
}
$blocked = false;
$hidden = false;
$ignored = false;
$archived = false;
$all = false;
if (($a->argc == 2) && ($a->argv[1] === 'all')) {
$sql_extra = '';
$all = true;
} elseif (($a->argc == 2) && ($a->argv[1] === 'blocked')) {
$sql_extra = " AND `blocked` = 1 ";
$blocked = true;
} elseif (($a->argc == 2) && ($a->argv[1] === 'hidden')) {
$sql_extra = " AND `hidden` = 1 ";
$hidden = true;
} elseif (($a->argc == 2) && ($a->argv[1] === 'ignored')) {
$sql_extra = " AND `readonly` = 1 ";
$ignored = true;
} elseif (($a->argc == 2) && ($a->argv[1] === 'archived')) {
$sql_extra = " AND `archive` = 1 ";
$archived = true;
} else {
$sql_extra = " AND `blocked` = 0 ";
}
$sql_extra .= sprintf(" AND `network` != '%s' ", Protocol::PHANTOM);
$search = notags(trim(defaults($_GET, 'search', '')));
$nets = notags(trim(defaults($_GET, 'nets' , '')));
$tabs = [
[
'label' => L10n::t('Suggestions'),
'url' => 'suggest',
'sel' => '',
'title' => L10n::t('Suggest potential friends'),
'id' => 'suggestions-tab',
'accesskey' => 'g',
],
[
'label' => L10n::t('All Contacts'),
'url' => 'contact/all',
'sel' => ($all) ? 'active' : '',
'title' => L10n::t('Show all contacts'),
'id' => 'showall-tab',
'accesskey' => 'l',
],
[
'label' => L10n::t('Unblocked'),
'url' => 'contact',
'sel' => ((!$all) && (!$blocked) && (!$hidden) && (!$search) && (!$nets) && (!$ignored) && (!$archived)) ? 'active' : '',
'title' => L10n::t('Only show unblocked contacts'),
'id' => 'showunblocked-tab',
'accesskey' => 'o',
],
[
'label' => L10n::t('Blocked'),
'url' => 'contact/blocked',
'sel' => ($blocked) ? 'active' : '',
'title' => L10n::t('Only show blocked contacts'),
'id' => 'showblocked-tab',
'accesskey' => 'b',
],
[
'label' => L10n::t('Ignored'),
'url' => 'contact/ignored',
'sel' => ($ignored) ? 'active' : '',
'title' => L10n::t('Only show ignored contacts'),
'id' => 'showignored-tab',
'accesskey' => 'i',
],
[
'label' => L10n::t('Archived'),
'url' => 'contact/archived',
'sel' => ($archived) ? 'active' : '',
'title' => L10n::t('Only show archived contacts'),
'id' => 'showarchived-tab',
'accesskey' => 'y',
],
[
'label' => L10n::t('Hidden'),
'url' => 'contact/hidden',
'sel' => ($hidden) ? 'active' : '',
'title' => L10n::t('Only show hidden contacts'),
'id' => 'showhidden-tab',
'accesskey' => 'h',
],
];
$tab_tpl = get_markup_template('common_tabs.tpl');
$t = replace_macros($tab_tpl, ['$tabs' => $tabs]);
$total = 0;
$searching = false;
$search_hdr = null;
if ($search) {
$searching = true;
$search_hdr = $search;
$search_txt = DBA::escape(protect_sprintf(preg_quote($search)));
$sql_extra .= " AND (name REGEXP '$search_txt' OR url REGEXP '$search_txt' OR nick REGEXP '$search_txt') ";
}
if ($nets) {
$sql_extra .= sprintf(" AND network = '%s' ", DBA::escape($nets));
}
$sql_extra2 = ((($sort_type > 0) && ($sort_type <= Model\Contact::FRIEND)) ? sprintf(" AND `rel` = %d ", intval($sort_type)) : '');
$r = q("SELECT COUNT(*) AS `total` FROM `contact`
WHERE `uid` = %d AND `self` = 0 AND `pending` = 0 $sql_extra $sql_extra2 ",
intval($_SESSION['uid'])
);
if (DBA::isResult($r)) {
$a->setPagerTotal($r[0]['total']);
$total = $r[0]['total'];
}
$sql_extra3 = Widget::unavailableNetworks();
$contacts = [];
$r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `pending` = 0 $sql_extra $sql_extra2 $sql_extra3 ORDER BY `name` ASC LIMIT %d , %d ",
intval($_SESSION['uid']),
intval($a->pager['start']),
intval($a->pager['itemspage'])
);
if (DBA::isResult($r)) {
foreach ($r as $rr) {
$rr['blocked'] = Model\Contact::isBlockedByUser($rr['id'], local_user());
$rr['readonly'] = Model\Contact::isIgnoredByUser($rr['id'], local_user());
$contacts[] = self::getContactTemplateVars($rr);
}
}
$tpl = get_markup_template('contacts-template.tpl');
$o .= replace_macros($tpl, [
'$baseurl' => System::baseUrl(),
'$header' => L10n::t('Contacts') . (($nets) ? ' - ' . ContactSelector::networkToName($nets) : ''),
'$tabs' => $t,
'$total' => $total,
'$search' => $search_hdr,
'$desc' => L10n::t('Search your contacts'),
'$finding' => $searching ? L10n::t('Results for: %s', $search) : '',
'$submit' => L10n::t('Find'),
'$cmd' => $a->cmd,
'$contacts' => $contacts,
'$contact_drop_confirm' => L10n::t('Do you really want to delete this contact?'),
'multiselect' => 1,
'$batch_actions' => [
'contacts_batch_update' => L10n::t('Update'),
'contacts_batch_block' => L10n::t('Block') . '/' . L10n::t('Unblock'),
'contacts_batch_ignore' => L10n::t('Ignore') . '/' . L10n::t('Unignore'),
'contacts_batch_archive' => L10n::t('Archive') . '/' . L10n::t('Unarchive'),
'contacts_batch_drop' => L10n::t('Delete'),
],
'$h_batch_actions' => L10n::t('Batch Actions'),
'$paginate' => paginate($a),
]);
return $o;
}
/**
* @brief List of pages for the Contact TabBar
*
* Available Pages are 'Status', 'Profile', 'Contacts' and 'Common Friends'
*
* @param App $a
* @param array $contact The contact array
* @param int $active_tab 1 if tab should be marked as active
*
* @return string | HTML string of the contact page tabs buttons.
*/
public static function getTabsHTML($a, $contact, $active_tab)
{
// tabs
$tabs = [
[
'label' => L10n::t('Status'),
'url' => "contact/" . $contact['id'] . "/conversations",
'sel' => (($active_tab == 1) ? 'active' : ''),
'title' => L10n::t('Conversations started by this contact'),
'id' => 'status-tab',
'accesskey' => 'm',
],
[
'label' => L10n::t('Posts and Comments'),
'url' => "contact/" . $contact['id'] . "/posts",
'sel' => (($active_tab == 2) ? 'active' : ''),
'title' => L10n::t('Status Messages and Posts'),
'id' => 'posts-tab',
'accesskey' => 'p',
],
[
'label' => L10n::t('Profile'),
'url' => "contact/" . $contact['id'],
'sel' => (($active_tab == 3) ? 'active' : ''),
'title' => L10n::t('Profile Details'),
'id' => 'profile-tab',
'accesskey' => 'o',
]
];
// Show this tab only if there is visible friend list
$x = Model\GContact::countAllFriends(local_user(), $contact['id']);
if ($x) {
$tabs[] = ['label' => L10n::t('Contacts'),
'url' => "allfriends/" . $contact['id'],
'sel' => (($active_tab == 4) ? 'active' : ''),
'title' => L10n::t('View all contacts'),
'id' => 'allfriends-tab',
'accesskey' => 't'];
}
// Show this tab only if there is visible common friend list
$common = Model\GContact::countCommonFriends(local_user(), $contact['id']);
if ($common) {
$tabs[] = ['label' => L10n::t('Common Friends'),
'url' => "common/loc/" . local_user() . "/" . $contact['id'],
'sel' => (($active_tab == 5) ? 'active' : ''),
'title' => L10n::t('View all common friends'),
'id' => 'common-loc-tab',
'accesskey' => 'd'
];
}
if (!empty($contact['uid'])) {
$tabs[] = ['label' => L10n::t('Advanced'),
'url' => 'crepair/' . $contact['id'],
'sel' => (($active_tab == 6) ? 'active' : ''),
'title' => L10n::t('Advanced Contact Settings'),
'id' => 'advanced-tab',
'accesskey' => 'r'
];
}
$tab_tpl = get_markup_template('common_tabs.tpl');
$tab_str = replace_macros($tab_tpl, ['$tabs' => $tabs]);
return $tab_str;
}
private static function getConversationsHMTL($a, $contact_id, $update)
{
$o = '';
if (!$update) {
// We need the editor here to be able to reshare an item.
if (local_user()) {
$x = [
'is_owner' => true,
'allow_location' => $a->user['allow_location'],
'default_location' => $a->user['default-location'],
'nickname' => $a->user['nickname'],
'lockstate' => (is_array($a->user) && (strlen($a->user['allow_cid']) || strlen($a->user['allow_gid']) || strlen($a->user['deny_cid']) || strlen($a->user['deny_gid'])) ? 'lock' : 'unlock'),
'acl' => ACL::getFullSelectorHTML($a->user, true),
'bang' => '',
'visitor' => 'block',
'profile_uid' => local_user(),
];
$o = status_editor($a, $x, 0, true);
}
}
$contact = DBA::selectFirst('contact', ['uid', 'url', 'id'], ['id' => $contact_id]);
if (!$update) {
$o .= self::getTabsHTML($a, $contact, 1);
}
if (DBA::isResult($contact)) {
$a->page['aside'] = '';
$profiledata = Model\Contact::getDetailsByURL($contact['url']);
if (local_user() && in_array($profiledata['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
$profiledata['remoteconnect'] = System::baseUrl() . '/follow?url=' . urlencode($profiledata['url']);
}
Model\Profile::load($a, '', 0, $profiledata, true);
$o .= Model\Contact::getPostsFromUrl($contact['url'], true, $update);
}
return $o;
}
private static function getPostsHTML($a, $contact_id)
{
$contact = DBA::selectFirst('contact', ['uid', 'url', 'id'], ['id' => $contact_id]);
$o = self::getTabsHTML($a, $contact, 2);
if (DBA::isResult($contact)) {
$a->page['aside'] = '';
$profiledata = Model\Contact::getDetailsByURL($contact['url']);
if (local_user() && in_array($profiledata['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
$profiledata['remoteconnect'] = System::baseUrl() . '/follow?url=' . urlencode($profiledata['url']);
}
Model\Profile::load($a, '', 0, $profiledata, true);
$o .= Model\Contact::getPostsFromUrl($contact['url']);
}
return $o;
}
public static function getContactTemplateVars(array $rr)
{
$dir_icon = '';
$alt_text = '';
switch ($rr['rel']) {
case Model\Contact::FRIEND:
$dir_icon = 'images/lrarrow.gif';
$alt_text = L10n::t('Mutual Friendship');
break;
case Model\Contact::FOLLOWER;
$dir_icon = 'images/larrow.gif';
$alt_text = L10n::t('is a fan of yours');
break;
case Model\Contact::SHARING;
$dir_icon = 'images/rarrow.gif';
$alt_text = L10n::t('you are a fan of');
break;
default:
break;
}
$url = Model\Contact::magicLink($rr['url']);
if (strpos($url, 'redir/') === 0) {
$sparkle = ' class="sparkle" ';
} else {
$sparkle = '';
}
if ($rr['self']) {
$dir_icon = 'images/larrow.gif';
$alt_text = L10n::t('This is you');
$url = $rr['url'];
$sparkle = '';
}
return [
'img_hover' => L10n::t('Visit %s\'s profile [%s]', $rr['name'], $rr['url']),
'edit_hover'=> L10n::t('Edit contact'),
'photo_menu'=> Model\Contact::photoMenu($rr),
'id' => $rr['id'],
'alt_text' => $alt_text,
'dir_icon' => $dir_icon,
'thumb' => ProxyUtils::proxifyUrl($rr['thumb'], false, ProxyUtils::SIZE_THUMB),
'name' => htmlentities($rr['name']),
'username' => htmlentities($rr['name']),
'account_type' => Model\Contact::getAccountType($rr),
'sparkle' => $sparkle,
'itemurl' => defaults($rr, 'addr', $rr['url']),
'url' => $url,
'network' => ContactSelector::networkToName($rr['network'], $rr['url']),
'nick' => htmlentities($rr['nick']),
];
}
/**
* @brief Gives a array with actions which can performed to a given contact
*
* This includes actions like e.g. 'block', 'hide', 'archive', 'delete' and others
*
* @param array $contact Data about the Contact
* @return array with contact related actions
*/
private static function getContactActions($contact)
{
$poll_enabled = in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
$contact_actions = [];
// Provide friend suggestion only for Friendica contacts
if ($contact['network'] === Protocol::DFRN) {
$contact_actions['suggest'] = [
'label' => L10n::t('Suggest friends'),
'url' => 'fsuggest/' . $contact['id'],
'title' => '',
'sel' => '',
'id' => 'suggest',
];
}
if ($poll_enabled) {
$contact_actions['update'] = [
'label' => L10n::t('Update now'),
'url' => 'contact/' . $contact['id'] . '/update',
'title' => '',
'sel' => '',
'id' => 'update',
];
}
$contact_actions['block'] = [
'label' => (intval($contact['blocked']) ? L10n::t('Unblock') : L10n::t('Block')),
'url' => 'contact/' . $contact['id'] . '/block',
'title' => L10n::t('Toggle Blocked status'),
'sel' => (intval($contact['blocked']) ? 'active' : ''),
'id' => 'toggle-block',
];
$contact_actions['ignore'] = [
'label' => (intval($contact['readonly']) ? L10n::t('Unignore') : L10n::t('Ignore')),
'url' => 'contact/' . $contact['id'] . '/ignore',
'title' => L10n::t('Toggle Ignored status'),
'sel' => (intval($contact['readonly']) ? 'active' : ''),
'id' => 'toggle-ignore',
];
if ($contact['uid'] != 0) {
$contact_actions['archive'] = [
'label' => (intval($contact['archive']) ? L10n::t('Unarchive') : L10n::t('Archive')),
'url' => 'contact/' . $contact['id'] . '/archive',
'title' => L10n::t('Toggle Archive status'),
'sel' => (intval($contact['archive']) ? 'active' : ''),
'id' => 'toggle-archive',
];
$contact_actions['delete'] = [
'label' => L10n::t('Delete'),
'url' => 'contact/' . $contact['id'] . '/drop',
'title' => L10n::t('Delete contact'),
'sel' => '',
'id' => 'delete',
];
}
return $contact_actions;
}
}

View file

@ -20,7 +20,7 @@ use Friendica\Protocol\OStatus;
*
* @brief Provides public Atom feeds
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class Feed extends BaseModule
{

38
src/Module/Followers.php Normal file
View file

@ -0,0 +1,38 @@
<?php
/**
* @file src/Module/Followers.php
*/
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Protocol\ActivityPub;
use Friendica\Core\System;
use Friendica\Model\User;
/**
* ActivityPub Followers
*/
class Followers extends BaseModule
{
public static function rawContent()
{
$a = self::getApp();
if (empty($a->argv[1])) {
System::httpExit(404);
}
$owner = User::getOwnerDataByNick($a->argv[1]);
if (empty($owner)) {
System::httpExit(404);
}
$page = defaults($_REQUEST, 'page', null);
$followers = ActivityPub\Transmitter::getFollowers($owner, $page);
header('Content-Type: application/activity+json');
echo json_encode($followers);
exit();
}
}

38
src/Module/Following.php Normal file
View file

@ -0,0 +1,38 @@
<?php
/**
* @file src/Module/Following.php
*/
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Protocol\ActivityPub;
use Friendica\Core\System;
use Friendica\Model\User;
/**
* ActivityPub Following
*/
class Following extends BaseModule
{
public static function rawContent()
{
$a = self::getApp();
if (empty($a->argv[1])) {
System::httpExit(404);
}
$owner = User::getOwnerDataByNick($a->argv[1]);
if (empty($owner)) {
System::httpExit(404);
}
$page = defaults($_REQUEST, 'page', null);
$Following = ActivityPub\Transmitter::getFollowing($owner, $page);
header('Content-Type: application/activity+json');
echo json_encode($Following);
exit();
}
}

55
src/Module/Inbox.php Normal file
View file

@ -0,0 +1,55 @@
<?php
/**
* @file src/Module/Inbox.php
*/
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Protocol\ActivityPub;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Util\HTTPSignature;
/**
* ActivityPub Inbox
*/
class Inbox extends BaseModule
{
public static function rawContent()
{
$a = self::getApp();
$postdata = file_get_contents('php://input');
if (empty($postdata)) {
System::httpExit(400);
}
// Enable for test purposes
/*
if (HTTPSignature::getSigner($postdata, $_SERVER)) {
$filename = 'signed-activitypub';
} else {
$filename = 'failed-activitypub';
}
$tempfile = tempnam(get_temppath(), $filename);
file_put_contents($tempfile, json_encode(['argv' => $a->argv, 'header' => $_SERVER, 'body' => $postdata], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
logger('Incoming message stored under ' . $tempfile);
*/
if (!empty($a->argv[1])) {
$user = DBA::selectFirst('user', ['uid'], ['nickname' => $a->argv[1]]);
if (!DBA::isResult($user)) {
System::httpExit(404);
}
$uid = $user['uid'];
} else {
$uid = 0;
}
ActivityPub\Receiver::processInbox($postdata, $_SERVER, $uid);
System::httpExit(202);
}
}

View file

@ -22,7 +22,7 @@ require_once 'include/text.php';
/**
* Login module
*
* @author Hypolite Petovan mrpetovan@gmail.com
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class Login extends BaseModule
{
@ -39,15 +39,18 @@ class Login extends BaseModule
}
if (local_user()) {
goaway(self::getApp()->get_baseurl());
goaway(self::getApp()->getBaseURL());
}
return self::form(self::getApp()->get_baseurl(), intval(Config::get('config', 'register_policy')) !== REGISTER_CLOSED);
return self::form($_SESSION['return_url'], intval(Config::get('config', 'register_policy')) !== REGISTER_CLOSED);
}
public static function post()
{
$return_url = $_SESSION['return_url'];
session_unset();
$_SESSION['return_url'] = $return_url;
// OpenId Login
if (
empty($_POST['password'])
@ -83,18 +86,18 @@ class Login extends BaseModule
// if it's an email address or doesn't resolve to a URL, fail.
if ($noid || strpos($openid_url, '@') || !Network::isUrlValid($openid_url)) {
notice(L10n::t('Login failed.') . EOL);
goaway(self::getApp()->get_baseurl());
goaway(self::getApp()->getBaseURL());
// NOTREACHED
}
// Otherwise it's probably an openid.
try {
$a = get_app();
$openid = new LightOpenID($a->get_hostname());
$openid = new LightOpenID($a->getHostName());
$openid->identity = $openid_url;
$_SESSION['openid'] = $openid_url;
$_SESSION['remember'] = $remember;
$openid->returnUrl = self::getApp()->get_baseurl(true) . '/openid';
$openid->returnUrl = self::getApp()->getBaseURL(true) . '/openid';
goaway($openid->authUrl());
} catch (Exception $e) {
notice(L10n::t('We encountered a problem while logging in with the OpenID you provided. Please check the correct spelling of the ID.') . '<br /><br >' . L10n::t('The error message was:') . ' ' . $e->getMessage());
@ -140,8 +143,8 @@ class Login extends BaseModule
}
} catch (Exception $e) {
logger('authenticate: failed login attempt: ' . notags($username) . ' from IP ' . $_SERVER['REMOTE_ADDR']);
notice($e->getMessage() . EOL);
goaway(self::getApp()->get_baseurl() . '/login');
info('Login failed. Please check your credentials.' . EOL);
goaway('/');
}
if (!$remember) {
@ -188,7 +191,7 @@ class Login extends BaseModule
if ($data->hash != cookie_hash($user)) {
logger("Hash for user " . $data->uid . " doesn't fit.");
nuke_session();
goaway(self::getApp()->get_baseurl());
goaway(self::getApp()->getBaseURL());
}
// Renew the cookie
@ -225,7 +228,7 @@ class Login extends BaseModule
logger('Session address changed. Paranoid setting in effect, blocking session. ' .
$_SESSION['addr'] . ' != ' . $_SERVER['REMOTE_ADDR']);
nuke_session();
goaway(self::getApp()->get_baseurl());
goaway(self::getApp()->getBaseURL());
}
$user = DBA::selectFirst('user', [],
@ -239,7 +242,7 @@ class Login extends BaseModule
);
if (!DBA::isResult($user)) {
nuke_session();
goaway(self::getApp()->get_baseurl());
goaway(self::getApp()->getBaseURL());
}
// Make sure to refresh the last login time for the user if the user
@ -294,7 +297,7 @@ class Login extends BaseModule
$a->page['htmlhead'] .= replace_macros(
get_markup_template('login_head.tpl'),
[
'$baseurl' => $a->get_baseurl(true)
'$baseurl' => $a->getBaseURL(true)
]
);
@ -305,7 +308,7 @@ class Login extends BaseModule
$o .= replace_macros(
$tpl,
[
'$dest_url' => self::getApp()->get_baseurl(true) . '/login',
'$dest_url' => self::getApp()->getBaseURL(true) . '/login',
'$logout' => L10n::t('Logout'),
'$login' => L10n::t('Login'),

View file

@ -14,7 +14,7 @@ require_once 'include/security.php';
/**
* Logout module
*
* @author Hypolite Petovan mrpetovan@gmail.com
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class Logout extends BaseModule
{
@ -26,6 +26,6 @@ class Logout extends BaseModule
Addon::callHooks("logging_out");
nuke_session();
info(L10n::t('Logged out.') . EOL);
goaway(self::getApp()->get_baseurl());
goaway(self::getApp()->getBaseURL());
}
}

View file

@ -47,7 +47,7 @@ class Magic extends BaseModule
$contact = DBA::selectFirst('contact', ['id', 'nurl', 'url'], ['id' => $cid]);
// Redirect if the contact is already authenticated on this site.
if (!empty($a->contact) && array_key_exists('id', $a->contact) && strpos($contact['nurl'], normalise_link(self::getApp()->get_baseurl())) !== false) {
if (!empty($a->contact) && array_key_exists('id', $a->contact) && strpos($contact['nurl'], normalise_link(self::getApp()->getBaseURL())) !== false) {
if ($test) {
$ret['success'] = true;
$ret['message'] .= 'Local site - you are already authenticated.' . EOL;
@ -76,20 +76,16 @@ class Magic extends BaseModule
// Create a header that is signed with the local users private key.
$headers = HTTPSignature::createSig(
'',
$headers,
$user['prvkey'],
'acct:' . $user['nickname'] . '@' . $a->get_hostname() . ($a->urlpath ? '/' . $a->urlpath : ''),
false,
true,
'sha512'
'acct:' . $user['nickname'] . '@' . $a->getHostName() . ($a->getURLPath() ? '/' . $a->getURLPath() : '')
);
// Try to get an authentication token from the other instance.
$x = Network::curl($basepath . '/owa', false, $redirects, ['headers' => $headers]);
$curlResult = Network::curl($basepath . '/owa', false, $redirects, ['headers' => $headers]);
if ($x['success']) {
$j = json_decode($x['body'], true);
if ($curlResult->isSuccess()) {
$j = json_decode($curlResult->getBody(), true);
if ($j['success']) {
$token = '';

41
src/Module/Objects.php Normal file
View file

@ -0,0 +1,41 @@
<?php
/**
* @file src/Module/Objects.php
*/
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Protocol\ActivityPub;
use Friendica\Core\System;
use Friendica\Model\Item;
use Friendica\Database\DBA;
/**
* ActivityPub Objects
*/
class Objects extends BaseModule
{
public static function rawContent()
{
$a = self::getApp();
if (empty($a->argv[1])) {
System::httpExit(404);
}
if (!ActivityPub::isRequest()) {
goaway(str_replace('objects/', 'display/', $a->query_string));
}
$item = Item::selectFirst(['id'], ['guid' => $a->argv[1], 'wall' => true, 'private' => false]);
if (!DBA::isResult($item)) {
System::httpExit(404);
}
$data = ActivityPub\Transmitter::createObjectFromItemID($item['id']);
header('Content-Type: application/activity+json');
echo json_encode($data);
exit();
}
}

View file

@ -12,7 +12,7 @@ use Friendica\Content;
*
* Example: /oembed/aHR0cHM6Ly9...
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class Oembed extends BaseModule
{

38
src/Module/Outbox.php Normal file
View file

@ -0,0 +1,38 @@
<?php
/**
* @file src/Module/Outbox.php
*/
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Protocol\ActivityPub;
use Friendica\Core\System;
use Friendica\Model\User;
/**
* ActivityPub Outbox
*/
class Outbox extends BaseModule
{
public static function rawContent()
{
$a = self::getApp();
if (empty($a->argv[1])) {
System::httpExit(404);
}
$owner = User::getOwnerDataByNick($a->argv[1]);
if (empty($owner)) {
System::httpExit(404);
}
$page = defaults($_REQUEST, 'page', null);
$outbox = ActivityPub\Transmitter::getOutbox($owner, $page);
header('Content-Type: application/activity+json');
echo json_encode($outbox);
exit();
}
}

View file

@ -54,7 +54,7 @@ class Owa extends BaseModule
if (DBA::isResult($contact)) {
// Try to verify the signed header with the public key of the contact record
// we have found.
$verified = HTTPSignature::verify('', $contact['pubkey']);
$verified = HTTPSignature::verifyMagic($contact['pubkey']);
if ($verified && $verified['header_signed'] && $verified['header_valid']) {
logger('OWA header: ' . print_r($verified, true), LOGGER_DATA);

View file

@ -8,6 +8,7 @@ namespace Friendica\Module;
use Friendica\App;
use Friendica\BaseModule;
use Friendica\Core\Config;
use Friendica\Core\L10n;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Model\Photo;
@ -70,7 +71,7 @@ class Proxy extends BaseModule
$thumb = false;
$size = 1024;
$sizetype = '';
$basepath = $a->get_basepath();
$basepath = $a->getBasePath();
// If the cache path isn't there, try to create it
if (!is_dir($basepath . '/proxy') && is_writable($basepath)) {
@ -133,6 +134,10 @@ class Proxy extends BaseModule
$direct_cache = false;
}
if (empty($_REQUEST['url'])) {
System::httpExit(400, ["title" => L10n::t('Bad Request.')]);
}
if (!$direct_cache) {
$urlhash = 'pic:' . sha1($_REQUEST['url']);
@ -183,7 +188,8 @@ class Proxy extends BaseModule
// It shouldn't happen but it does - spaces in URL
$_REQUEST['url'] = str_replace(' ', '+', $_REQUEST['url']);
$redirects = 0;
$img_str = Network::fetchUrl($_REQUEST['url'], true, $redirects, 10);
$fetchResult = Network::fetchUrlFull($_REQUEST['url'], true, $redirects, 10);
$img_str = $fetchResult->getBody();
$tempfile = tempnam(get_temppath(), 'cache');
file_put_contents($tempfile, $img_str);
@ -191,7 +197,7 @@ class Proxy extends BaseModule
unlink($tempfile);
// If there is an error then return a blank image
if ((substr($a->get_curl_code(), 0, 1) == '4') || (!$img_str)) {
if ((substr($fetchResult->getReturnCode(), 0, 1) == '4') || (!$img_str)) {
$img_str = file_get_contents('images/blank.png');
$mime = 'image/png';
$cachefile = ''; // Clear the cachefile so that the dummy isn't stored

296
src/Network/CurlResult.php Normal file
View file

@ -0,0 +1,296 @@
<?php
namespace Friendica\Network;
use Friendica\Network\HTTPException\InternalServerErrorException;
/**
* A content class for Curl call results
*/
class CurlResult
{
/**
* @var int HTTP return code or 0 if timeout or failure
*/
private $returnCode;
/**
* @var string the content type of the Curl call
*/
private $contentType;
/**
* @var string the HTTP headers of the Curl call
*/
private $header;
/**
* @var boolean true (if HTTP 2xx result) or false
*/
private $isSuccess;
/**
* @var string the URL which was called
*/
private $url;
/**
* @var string in case of redirect, content was finally retrieved from this URL
*/
private $redirectUrl;
/**
* @var string fetched content
*/
private $body;
/**
* @var array some informations about the fetched data
*/
private $info;
/**
* @var boolean true if the URL has a redirect
*/
private $isRedirectUrl;
/**
* @var boolean true if the curl request timed out
*/
private $isTimeout;
/**
* @var int the error number or 0 (zero) if no error
*/
private $errorNumber;
/**
* @var string the error message or '' (the empty string) if no
*/
private $error;
/**
* Creates an errored CURL response
*
* @param string $url optional URL
*
* @return CurlResult a CURL with error response
*/
public static function createErrorCurl($url = '')
{
return new CurlResult($url, '', ['http_code' => 0]);
}
/**
* Curl constructor.
* @param string $url the URL which was called
* @param string $result the result of the curl execution
* @param array $info an additional info array
* @param int $errorNumber the error number or 0 (zero) if no error
* @param string $error the error message or '' (the empty string) if no
*
* @throws InternalServerErrorException when HTTP code of the CURL response is missing
*/
public function __construct($url, $result, $info, $errorNumber = 0, $error = '')
{
if (!array_key_exists('http_code', $info)) {
throw new InternalServerErrorException('CURL response doesn\'t contains a response HTTP code');
}
$this->returnCode = $info['http_code'];
$this->url = $url;
$this->info = $info;
$this->errorNumber = $errorNumber;
$this->error = $error;
logger($url . ': ' . $this->returnCode . " " . $result, LOGGER_DATA);
$this->parseBodyHeader($result);
$this->checkSuccess();
$this->checkRedirect();
$this->checkInfo();
}
private function parseBodyHeader($result)
{
// Pull out multiple headers, e.g. proxy and continuation headers
// allow for HTTP/2.x without fixing code
$header = '';
$base = $result;
while (preg_match('/^HTTP\/.+? \d+/', $base)) {
$chunk = substr($base, 0, strpos($base, "\r\n\r\n") + 4);
$header .= $chunk;
$base = substr($base, strlen($chunk));
}
$this->body = substr($result, strlen($header));
$this->header = $header;
}
private function checkSuccess()
{
$this->isSuccess = ($this->returnCode >= 200 && $this->returnCode <= 299) || $this->errorNumber == 0;
if (!$this->isSuccess) {
logger('error: ' . $this->url . ': ' . $this->returnCode . ' - ' . $this->error, LOGGER_INFO);
logger('debug: ' . print_r($this->info, true), LOGGER_DATA);
}
if (!$this->isSuccess && $this->errorNumber == CURLE_OPERATION_TIMEDOUT) {
$this->isTimeout = true;
} else {
$this->isTimeout = false;
}
}
private function checkRedirect()
{
if (!array_key_exists('url', $this->info)) {
$this->redirectUrl = '';
} else {
$this->redirectUrl = $this->info['url'];
}
if ($this->returnCode == 301 || $this->returnCode == 302 || $this->returnCode == 303 || $this->returnCode== 307) {
$new_location_info = (!array_key_exists('redirect_url', $this->info) ? '' : @parse_url($this->info['redirect_url']));
$old_location_info = (!array_key_exists('url', $this->info) ? '' : @parse_url($this->info['url']));
$this->redirectUrl = $new_location_info;
if (empty($new_location_info['path']) && !empty($new_location_info['host'])) {
$this->redirectUrl = $new_location_info['scheme'] . '://' . $new_location_info['host'] . $old_location_info['path'];
}
$matches = [];
if (preg_match('/(Location:|URI:)(.*?)\n/i', $this->header, $matches)) {
$this->redirectUrl = trim(array_pop($matches));
}
if (strpos($this->redirectUrl, '/') === 0) {
$this->redirectUrl = $old_location_info["scheme"] . "://" . $old_location_info["host"] . $this->redirectUrl;
}
$old_location_query = @parse_url($this->url, PHP_URL_QUERY);
if ($old_location_query != '') {
$this->redirectUrl .= '?' . $old_location_query;
}
$this->isRedirectUrl = filter_var($this->redirectUrl, FILTER_VALIDATE_URL) !== false;
} else {
$this->isRedirectUrl = false;
}
}
private function checkInfo()
{
if (isset($this->info['content_type'])) {
$this->contentType = $this->info['content_type'];
} else {
$this->contentType = '';
}
}
/**
* Gets the Curl Code
*
* @return string The Curl Code
*/
public function getReturnCode()
{
return $this->returnCode;
}
/**
* Returns the Curl Content Type
*
* @return string the Curl Content Type
*/
public function getContentType()
{
return $this->contentType;
}
/**
* Returns the Curl headers
*
* @return string the Curl headers
*/
public function getHeader()
{
return $this->header;
}
/**
* @return bool
*/
public function isSuccess()
{
return $this->isSuccess;
}
/**
* @return string
*/
public function getUrl()
{
return $this->url;
}
/**
* @return string
*/
public function getRedirectUrl()
{
return $this->redirectUrl;
}
/**
* @return string
*/
public function getBody()
{
return $this->body;
}
/**
* @return array
*/
public function getInfo()
{
return $this->info;
}
/**
* @return bool
*/
public function isRedirectUrl()
{
return $this->isRedirectUrl;
}
/**
* @return int
*/
public function getErrorNumber()
{
return $this->errorNumber;
}
/**
* @return string
*/
public function getError()
{
return $this->error;
}
/**
* @return bool
*/
public function isTimeout()
{
return $this->isTimeout;
}
}

View file

@ -19,6 +19,7 @@ use Friendica\Model\Contact;
use Friendica\Model\Profile;
use Friendica\Protocol\Email;
use Friendica\Protocol\Feed;
use Friendica\Protocol\ActivityPub;
use Friendica\Util\Crypto;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Network;
@ -73,7 +74,7 @@ class Probe
*/
private static function ownHost($host)
{
$own_host = get_app()->get_hostname();
$own_host = get_app()->getHostName();
$parts = parse_url($host);
@ -111,20 +112,20 @@ class Probe
logger("Probing for ".$host, LOGGER_DEBUG);
$xrd = null;
$ret = Network::curl($ssl_url, false, $redirects, ['timeout' => $xrd_timeout, 'accept_content' => 'application/xrd+xml']);
if ($ret['success']) {
$xml = $ret['body'];
$curlResult = Network::curl($ssl_url, false, $redirects, ['timeout' => $xrd_timeout, 'accept_content' => 'application/xrd+xml']);
if ($curlResult->isSuccess()) {
$xml = $curlResult->getBody();
$xrd = XML::parseString($xml, false);
$host_url = 'https://'.$host;
}
if (!is_object($xrd)) {
$ret = Network::curl($url, false, $redirects, ['timeout' => $xrd_timeout, 'accept_content' => 'application/xrd+xml']);
if (!empty($ret["errno"]) && ($ret['errno'] == CURLE_OPERATION_TIMEDOUT)) {
logger("Probing timeout for ".$url, LOGGER_DEBUG);
$curlResult = Network::curl($url, false, $redirects, ['timeout' => $xrd_timeout, 'accept_content' => 'application/xrd+xml']);
if ($curlResult->isTimeout()) {
logger("Probing timeout for " . $url, LOGGER_DEBUG);
return false;
}
$xml = $ret['body'];
$xml = $curlResult->getBody();
$xrd = XML::parseString($xml, false);
$host_url = 'http://'.$host;
}
@ -328,7 +329,17 @@ class Probe
$uid = local_user();
}
$data = self::detect($uri, $network, $uid);
if ($network != Protocol::ACTIVITYPUB) {
$data = self::detect($uri, $network, $uid);
} else {
$data = null;
}
$ap_profile = ActivityPub::probeProfile($uri);
if (!empty($ap_profile) && (defaults($data, 'network', '') != Protocol::DFRN)) {
$data = $ap_profile;
}
if (!isset($data["url"])) {
$data["url"] = $uri;
@ -731,11 +742,11 @@ class Probe
$xrd_timeout = Config::get('system', 'xrd_timeout', 20);
$redirects = 0;
$ret = Network::curl($url, false, $redirects, ['timeout' => $xrd_timeout, 'accept_content' => $type]);
if (!empty($ret["errno"]) && ($ret['errno'] == CURLE_OPERATION_TIMEDOUT)) {
$curlResult = Network::curl($url, false, $redirects, ['timeout' => $xrd_timeout, 'accept_content' => $type]);
if ($curlResult->isTimeout()) {
return false;
}
$data = $ret['body'];
$data = $curlResult->getBody();
$webfinger = json_decode($data, true);
if (is_array($webfinger)) {
@ -798,11 +809,11 @@ class Probe
*/
private static function pollNoscrape($noscrape_url, $data)
{
$ret = Network::curl($noscrape_url);
if (!empty($ret["errno"]) && ($ret['errno'] == CURLE_OPERATION_TIMEDOUT)) {
$curlResult = Network::curl($noscrape_url);
if ($curlResult->isTimeout()) {
return false;
}
$content = $ret['body'];
$content = $curlResult->getBody();
if (!$content) {
logger("Empty body for ".$noscrape_url, LOGGER_DEBUG);
return false;
@ -942,13 +953,13 @@ class Probe
$prof_data["addr"] = $data["addr"];
$prof_data["nick"] = $data["nick"];
$prof_data["dfrn-request"] = $data["request"];
$prof_data["dfrn-confirm"] = $data["confirm"];
$prof_data["dfrn-notify"] = $data["notify"];
$prof_data["dfrn-poll"] = $data["poll"];
$prof_data["photo"] = $data["photo"];
$prof_data["fn"] = $data["name"];
$prof_data["key"] = $data["pubkey"];
$prof_data["dfrn-request"] = defaults($data, 'request', null);
$prof_data["dfrn-confirm"] = defaults($data, 'confirm', null);
$prof_data["dfrn-notify"] = defaults($data, 'notify' , null);
$prof_data["dfrn-poll"] = defaults($data, 'poll' , null);
$prof_data["photo"] = defaults($data, 'photo' , null);
$prof_data["fn"] = defaults($data, 'name' , null);
$prof_data["key"] = defaults($data, 'pubkey' , null);
logger("Result for profile ".$profile_link.": ".print_r($prof_data, true), LOGGER_DEBUG);
@ -967,23 +978,23 @@ class Probe
$hcard_url = "";
$data = [];
foreach ($webfinger["links"] as $link) {
if (($link["rel"] == NAMESPACE_DFRN) && ($link["href"] != "")) {
if (($link["rel"] == NAMESPACE_DFRN) && !empty($link["href"])) {
$data["network"] = Protocol::DFRN;
} elseif (($link["rel"] == NAMESPACE_FEED) && ($link["href"] != "")) {
} elseif (($link["rel"] == NAMESPACE_FEED) && !empty($link["href"])) {
$data["poll"] = $link["href"];
} elseif (($link["rel"] == "http://webfinger.net/rel/profile-page") && ($link["type"] == "text/html") && ($link["href"] != "")) {
} elseif (($link["rel"] == "http://webfinger.net/rel/profile-page") && (defaults($link, "type", "") == "text/html") && !empty($link["href"])) {
$data["url"] = $link["href"];
} elseif (($link["rel"] == "http://microformats.org/profile/hcard") && ($link["href"] != "")) {
} elseif (($link["rel"] == "http://microformats.org/profile/hcard") && !empty($link["href"])) {
$hcard_url = $link["href"];
} elseif (($link["rel"] == NAMESPACE_POCO) && ($link["href"] != "")) {
} elseif (($link["rel"] == NAMESPACE_POCO) && !empty($link["href"])) {
$data["poco"] = $link["href"];
} elseif (($link["rel"] == "http://webfinger.net/rel/avatar") && ($link["href"] != "")) {
} elseif (($link["rel"] == "http://webfinger.net/rel/avatar") && !empty($link["href"])) {
$data["photo"] = $link["href"];
} elseif (($link["rel"] == "http://joindiaspora.com/seed_location") && ($link["href"] != "")) {
} elseif (($link["rel"] == "http://joindiaspora.com/seed_location") && !empty($link["href"])) {
$data["baseurl"] = trim($link["href"], '/');
} elseif (($link["rel"] == "http://joindiaspora.com/guid") && ($link["href"] != "")) {
} elseif (($link["rel"] == "http://joindiaspora.com/guid") && !empty($link["href"])) {
$data["guid"] = $link["href"];
} elseif (($link["rel"] == "diaspora-public-key") && ($link["href"] != "")) {
} elseif (($link["rel"] == "diaspora-public-key") && !empty($link["href"])) {
$data["pubkey"] = base64_decode($link["href"]);
//if (strstr($data["pubkey"], 'RSA ') || ($link["type"] == "RSA"))
@ -1043,11 +1054,11 @@ class Probe
*/
private static function pollHcard($hcard_url, $data, $dfrn = false)
{
$ret = Network::curl($hcard_url);
if (!empty($ret["errno"]) && ($ret['errno'] == CURLE_OPERATION_TIMEDOUT)) {
$curlResult = Network::curl($hcard_url);
if ($curlResult->isTimeout()) {
return false;
}
$content = $ret['body'];
$content = $curlResult->getBody();
if (!$content) {
return false;
}
@ -1170,21 +1181,21 @@ class Probe
$hcard_url = "";
$data = [];
foreach ($webfinger["links"] as $link) {
if (($link["rel"] == "http://microformats.org/profile/hcard") && ($link["href"] != "")) {
if (($link["rel"] == "http://microformats.org/profile/hcard") && !empty($link["href"])) {
$hcard_url = $link["href"];
} elseif (($link["rel"] == "http://joindiaspora.com/seed_location") && ($link["href"] != "")) {
} elseif (($link["rel"] == "http://joindiaspora.com/seed_location") && !empty($link["href"])) {
$data["baseurl"] = trim($link["href"], '/');
} elseif (($link["rel"] == "http://joindiaspora.com/guid") && ($link["href"] != "")) {
} elseif (($link["rel"] == "http://joindiaspora.com/guid") && !empty($link["href"])) {
$data["guid"] = $link["href"];
} elseif (($link["rel"] == "http://webfinger.net/rel/profile-page") && ($link["type"] == "text/html") && ($link["href"] != "")) {
} elseif (($link["rel"] == "http://webfinger.net/rel/profile-page") && (defaults($link, "type", "") == "text/html") && !empty($link["href"])) {
$data["url"] = $link["href"];
} elseif (($link["rel"] == NAMESPACE_FEED) && ($link["href"] != "")) {
} elseif (($link["rel"] == NAMESPACE_FEED) && !empty($link["href"])) {
$data["poll"] = $link["href"];
} elseif (($link["rel"] == NAMESPACE_POCO) && ($link["href"] != "")) {
} elseif (($link["rel"] == NAMESPACE_POCO) && !empty($link["href"])) {
$data["poco"] = $link["href"];
} elseif (($link["rel"] == "salmon") && ($link["href"] != "")) {
} elseif (($link["rel"] == "salmon") && !empty($link["href"])) {
$data["notify"] = $link["href"];
} elseif (($link["rel"] == "diaspora-public-key") && ($link["href"] != "")) {
} elseif (($link["rel"] == "diaspora-public-key") && !empty($link["href"])) {
$data["pubkey"] = base64_decode($link["href"]);
//if (strstr($data["pubkey"], 'RSA ') || ($link["type"] == "RSA"))
@ -1272,15 +1283,15 @@ class Probe
if (is_array($webfinger["links"])) {
foreach ($webfinger["links"] as $link) {
if (($link["rel"] == "http://webfinger.net/rel/profile-page")
&& ($link["type"] == "text/html")
&& (defaults($link, "type", "") == "text/html")
&& ($link["href"] != "")
) {
$data["url"] = $link["href"];
} elseif (($link["rel"] == "salmon") && ($link["href"] != "")) {
} elseif (($link["rel"] == "salmon") && !empty($link["href"])) {
$data["notify"] = $link["href"];
} elseif (($link["rel"] == NAMESPACE_FEED) && ($link["href"] != "")) {
} elseif (($link["rel"] == NAMESPACE_FEED) && !empty($link["href"])) {
$data["poll"] = $link["href"];
} elseif (($link["rel"] == "magic-public-key") && ($link["href"] != "")) {
} elseif (($link["rel"] == "magic-public-key") && !empty($link["href"])) {
$pubkey = $link["href"];
if (substr($pubkey, 0, 5) === 'data:') {
@ -1290,11 +1301,11 @@ class Probe
$pubkey = substr($pubkey, 5);
}
} elseif (normalise_link($pubkey) == 'http://') {
$ret = Network::curl($pubkey);
if (!empty($ret["errno"]) && ($ret['errno'] == CURLE_OPERATION_TIMEDOUT)) {
$curlResult = Network::curl($pubkey);
if ($curlResult->isTimeout()) {
return false;
}
$pubkey = $ret['body'];
$pubkey = $curlResult['body'];
}
$key = explode(".", $pubkey);
@ -1322,11 +1333,11 @@ class Probe
}
// Fetch all additional data from the feed
$ret = Network::curl($data["poll"]);
if (!empty($ret["errno"]) && ($ret['errno'] == CURLE_OPERATION_TIMEDOUT)) {
$curlResult = Network::curl($data["poll"]);
if ($curlResult->isTimeout()) {
return false;
}
$feed = $ret['body'];
$feed = $curlResult->getBody();
$dummy1 = null;
$dummy2 = null;
$dummy2 = null;
@ -1436,7 +1447,7 @@ class Probe
$data = [];
foreach ($webfinger["links"] as $link) {
if (($link["rel"] == "http://webfinger.net/rel/profile-page")
&& ($link["type"] == "text/html")
&& (defaults($link, "type", "") == "text/html")
&& ($link["href"] != "")
) {
$data["url"] = $link["href"];
@ -1532,11 +1543,11 @@ class Probe
*/
private static function feed($url, $probe = true)
{
$ret = Network::curl($url);
if (!empty($ret["errno"]) && ($ret['errno'] == CURLE_OPERATION_TIMEDOUT)) {
$curlResult = Network::curl($url);
if ($curlResult->isTimeout()) {
return false;
}
$feed = $ret['body'];
$feed = $curlResult->getBody();
$dummy1 = $dummy2 = $dummy3 = null;
$feed_data = Feed::import($feed, $dummy1, $dummy2, $dummy3, true);

View file

@ -655,7 +655,7 @@ class Image
$stamp1 = microtime(true);
file_put_contents($path, $string);
$a->save_timestamp($stamp1, "file");
$a->saveTimestamp($stamp1, "file");
}
/**
@ -720,17 +720,18 @@ class Image
*
* @param string $filename Image filename
* @param boolean $fromcurl Check Content-Type header from curl request
* @param string $header passed headers to take into account
*
* @return object
*/
public static function guessType($filename, $fromcurl = false)
public static function guessType($filename, $fromcurl = false, $header = '')
{
logger('Image: guessType: '.$filename . ($fromcurl?' from curl headers':''), LOGGER_DEBUG);
$type = null;
if ($fromcurl) {
$a = get_app();
$headers=[];
$h = explode("\n", $a->get_curl_headers());
$h = explode("\n", $header);
foreach ($h as $l) {
$data = array_map("trim", explode(":", trim($l), 2));
if (count($data) > 1) {
@ -792,14 +793,14 @@ class Image
try {
if (function_exists("getimagesizefromstring")) {
$data = getimagesizefromstring($img_str);
$data = @getimagesizefromstring($img_str);
} else {
$tempfile = tempnam(get_temppath(), "cache");
$a = get_app();
$stamp1 = microtime(true);
file_put_contents($tempfile, $img_str);
$a->save_timestamp($stamp1, "file");
$a->saveTimestamp($stamp1, "file");
$data = getimagesize($tempfile);
unlink($tempfile);
@ -907,7 +908,7 @@ class Image
$stamp1 = microtime(true);
$imagedata = @file_get_contents($url);
$a->save_timestamp($stamp1, "file");
$a->saveTimestamp($stamp1, "file");
}
$maximagesize = Config::get('system', 'maximagesize');
@ -921,7 +922,7 @@ class Image
$stamp1 = microtime(true);
file_put_contents($tempfile, $imagedata);
$a->save_timestamp($stamp1, "file");
$a->saveTimestamp($stamp1, "file");
$data = getimagesize($tempfile);

View file

@ -7,7 +7,7 @@ namespace Friendica\Object;
*
* @see https://oembed.com/#section2.3
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class OEmbed
{

View file

@ -324,7 +324,7 @@ class Post extends BaseObject
$owner_name_e = $this->getOwnerName();
// Disable features that aren't available in several networks
if (!in_array($item["network"], [Protocol::DFRN, Protocol::DIASPORA]) && isset($buttons["dislike"])) {
if (!in_array($item["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA]) && isset($buttons["dislike"])) {
unset($buttons["dislike"]);
$isevent = false;
$tagger = '';
@ -368,7 +368,7 @@ class Post extends BaseObject
'profile_url' => $profile_link,
'item_photo_menu' => item_photo_menu($item),
'name' => $name_e,
'thumb' => $a->remove_baseurl(ProxyUtils::proxifyUrl($item['author-avatar'], false, ProxyUtils::SIZE_THUMB)),
'thumb' => $a->removeBaseURL(ProxyUtils::proxifyUrl($item['author-avatar'], false, ProxyUtils::SIZE_THUMB)),
'osparkle' => $osparkle,
'sparkle' => $sparkle,
'title' => $title_e,
@ -381,7 +381,7 @@ class Post extends BaseObject
'indent' => $indent,
'shiny' => $shiny,
'owner_url' => $this->getOwnerUrl(),
'owner_photo' => $a->remove_baseurl(ProxyUtils::proxifyUrl($item['owner-avatar'], false, ProxyUtils::SIZE_THUMB)),
'owner_photo' => $a->removeBaseURL(ProxyUtils::proxifyUrl($item['owner-avatar'], false, ProxyUtils::SIZE_THUMB)),
'owner_name' => htmlentities($owner_name_e),
'plink' => get_plink($item),
'edpost' => Feature::isEnabled($conv->getProfileOwner(), 'edit_posts') ? $edpost : '',
@ -402,7 +402,7 @@ class Post extends BaseObject
'thread_level' => $thread_level,
'edited' => $edited,
'network' => $item["network"],
'network_name' => ContactSelector::networkToName($item['network'], $profile_link),
'network_name' => ContactSelector::networkToName($item['network'], $item['author-link']),
'received' => $item['received'],
'commented' => $item['commented'],
'created_date' => $item['created'],
@ -788,9 +788,9 @@ class Post extends BaseObject
'$parent' => $this->getId(),
'$qcomment' => $qcomment,
'$profile_uid' => $uid,
'$mylink' => $a->remove_baseurl($a->contact['url']),
'$mylink' => $a->removeBaseURL($a->contact['url']),
'$mytitle' => L10n::t('This is you'),
'$myphoto' => $a->remove_baseurl($a->contact['thumb']),
'$myphoto' => $a->removeBaseURL($a->contact['thumb']),
'$comment' => L10n::t('Comment'),
'$submit' => L10n::t('Submit'),
'$edbold' => L10n::t('Bold'),

View file

@ -0,0 +1,145 @@
<?php
/**
* @file src/Protocol/ActivityPub.php
*/
namespace Friendica\Protocol;
use Friendica\Util\Network;
use Friendica\Core\Protocol;
use Friendica\Model\APContact;
/**
* @brief ActivityPub Protocol class
* The ActivityPub Protocol is a message exchange protocol defined by the W3C.
* https://www.w3.org/TR/activitypub/
* https://www.w3.org/TR/activitystreams-core/
* https://www.w3.org/TR/activitystreams-vocabulary/
*
* https://blog.joinmastodon.org/2018/06/how-to-implement-a-basic-activitypub-server/
* https://blog.joinmastodon.org/2018/07/how-to-make-friends-and-verify-requests/
*
* Digest: https://tools.ietf.org/html/rfc5843
* https://tools.ietf.org/html/draft-cavage-http-signatures-10#ref-15
*
* Mastodon implementation of supported activities:
* https://github.com/tootsuite/mastodon/blob/master/app/lib/activitypub/activity.rb#L26
*
* Funkwhale:
* http://docs-funkwhale-funkwhale-549-music-federation-documentation.preview.funkwhale.audio/federation/index.html
*
* To-do:
* - Polling the outboxes for missing content?
*/
class ActivityPub
{
const PUBLIC_COLLECTION = 'https://www.w3.org/ns/activitystreams#Public';
const CONTEXT = ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1',
['vcard' => 'http://www.w3.org/2006/vcard/ns#',
'diaspora' => 'https://diasporafoundation.org/ns/',
'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
'sensitive' => 'as:sensitive', 'Hashtag' => 'as:Hashtag']];
const ACCOUNT_TYPES = ['Person', 'Organization', 'Service', 'Group', 'Application'];
/**
* Checks if the web request is done for the AP protocol
*
* @return is it AP?
*/
public static function isRequest()
{
return stristr(defaults($_SERVER, 'HTTP_ACCEPT', ''), 'application/activity+json') ||
stristr(defaults($_SERVER, 'HTTP_ACCEPT', ''), 'application/ld+json');
}
/**
* Fetches ActivityPub content from the given url
*
* @param string $url content url
* @return array
*/
public static function fetchContent($url)
{
$curlResult = Network::curl($url, false, $redirects, ['accept_content' => 'application/activity+json, application/ld+json']);
if (!$curlResult->isSuccess() || empty($curlResult->getBody())) {
return false;
}
$content = json_decode($curlResult->getBody(), true);
if (empty($content) || !is_array($content)) {
return false;
}
return $content;
}
/**
* Fetches a profile from the given url into an array that is compatible to Probe::uri
*
* @param string $url profile url
* @return array
*/
public static function probeProfile($url)
{
$apcontact = APContact::getByURL($url, true);
if (empty($apcontact)) {
return false;
}
$profile = ['network' => Protocol::ACTIVITYPUB];
$profile['nick'] = $apcontact['nick'];
$profile['name'] = $apcontact['name'];
$profile['guid'] = $apcontact['uuid'];
$profile['url'] = $apcontact['url'];
$profile['addr'] = $apcontact['addr'];
$profile['alias'] = $apcontact['alias'];
$profile['photo'] = $apcontact['photo'];
// $profile['community']
// $profile['keywords']
// $profile['location']
$profile['about'] = $apcontact['about'];
$profile['batch'] = $apcontact['sharedinbox'];
$profile['notify'] = $apcontact['inbox'];
$profile['poll'] = $apcontact['outbox'];
$profile['pubkey'] = $apcontact['pubkey'];
$profile['baseurl'] = $apcontact['baseurl'];
// Remove all "null" fields
foreach ($profile as $field => $content) {
if (is_null($content)) {
unset($profile[$field]);
}
}
return $profile;
}
/**
* Fetches activities from the outbox of a given profile and processes it
*
* @param string $url
* @param integer $uid User ID
*/
public static function fetchOutbox($url, $uid)
{
$data = self::fetchContent($url);
if (empty($data)) {
return;
}
if (!empty($data['orderedItems'])) {
$items = $data['orderedItems'];
} elseif (!empty($data['first']['orderedItems'])) {
$items = $data['first']['orderedItems'];
} elseif (!empty($data['first'])) {
self::fetchOutbox($data['first'], $uid);
return;
} else {
$items = [];
}
foreach ($items as $activity) {
$ldactivity = JsonLD::compact($activity);
ActivityPub\Receiver::processActivity($ldactivity, '', $uid, true);
}
}
}

View file

@ -0,0 +1,498 @@
<?php
/**
* @file src/Protocol/ActivityPub/Processor.php
*/
namespace Friendica\Protocol\ActivityPub;
use Friendica\Database\DBA;
use Friendica\Core\Protocol;
use Friendica\Model\Conversation;
use Friendica\Model\Contact;
use Friendica\Model\APContact;
use Friendica\Model\Item;
use Friendica\Model\User;
use Friendica\Content\Text\HTML;
use Friendica\Util\JsonLD;
use Friendica\Core\Config;
use Friendica\Protocol\ActivityPub;
/**
* ActivityPub Protocol class
*
* To-Do:
* - Store Diaspora signature
*/
class Processor
{
/**
* Converts mentions from Pleroma into the Friendica format
*
* @param string $body
*
* @return converted body
*/
private static function convertMentions($body)
{
$URLSearchString = "^\[\]";
$body = preg_replace("/\[url\=([$URLSearchString]*)\]([#@!])(.*?)\[\/url\]/ism", '$2[url=$1]$3[/url]', $body);
return $body;
}
/**
* Constructs a string with tags for a given tag array
*
* @param array $tags
* @param boolean $sensitive
*
* @return string with tags
*/
private static function constructTagList($tags, $sensitive)
{
if (empty($tags)) {
return '';
}
$tag_text = '';
foreach ($tags as $tag) {
if (in_array(defaults($tag, 'type', ''), ['Mention', 'Hashtag'])) {
if (!empty($tag_text)) {
$tag_text .= ',';
}
$tag_text .= substr($tag['name'], 0, 1) . '[url=' . $tag['href'] . ']' . substr($tag['name'], 1) . '[/url]';
}
}
/// @todo add nsfw for $sensitive
return $tag_text;
}
/**
* Add attachment data to the item array
*
* @param array $attachments
* @param array $item
*
* @return item array
*/
private static function constructAttachList($attachments, $item)
{
if (empty($attachments)) {
return $item;
}
foreach ($attachments as $attach) {
$filetype = strtolower(substr($attach['mediaType'], 0, strpos($attach['mediaType'], '/')));
if ($filetype == 'image') {
$item['body'] .= "\n[img]" . $attach['url'] . '[/img]';
} else {
if (!empty($item["attach"])) {
$item["attach"] .= ',';
} else {
$item["attach"] = '';
}
if (!isset($attach['length'])) {
$attach['length'] = "0";
}
$item["attach"] .= '[attach]href="'.$attach['url'].'" length="'.$attach['length'].'" type="'.$attach['mediaType'].'" title="'.defaults($attach, 'name', '').'"[/attach]';
}
}
return $item;
}
/**
* Prepares data for a message
*
* @param array $activity Activity array
*/
public static function createItem($activity)
{
$item = [];
$item['verb'] = ACTIVITY_POST;
$item['parent-uri'] = $activity['reply-to-id'];
if ($activity['reply-to-id'] == $activity['id']) {
$item['gravity'] = GRAVITY_PARENT;
$item['object-type'] = ACTIVITY_OBJ_NOTE;
} else {
$item['gravity'] = GRAVITY_COMMENT;
$item['object-type'] = ACTIVITY_OBJ_COMMENT;
}
if (($activity['id'] != $activity['reply-to-id']) && !Item::exists(['uri' => $activity['reply-to-id']])) {
logger('Parent ' . $activity['reply-to-id'] . ' not found. Try to refetch it.');
self::fetchMissingActivity($activity['reply-to-id'], $activity);
}
self::postItem($activity, $item);
}
/**
* Prepare the item array for a "like"
*
* @param array $activity Activity array
*/
public static function likeItem($activity)
{
$item = [];
$item['verb'] = ACTIVITY_LIKE;
$item['parent-uri'] = $activity['object_id'];
$item['gravity'] = GRAVITY_ACTIVITY;
$item['object-type'] = ACTIVITY_OBJ_NOTE;
self::postItem($activity, $item);
}
/**
* Delete items
*
* @param array $activity
*/
public static function deleteItem($activity)
{
$owner = Contact::getIdForURL($activity['actor']);
logger('Deleting item ' . $activity['object_id'] . ' from ' . $owner, LOGGER_DEBUG);
Item::delete(['uri' => $activity['object_id'], 'owner-id' => $owner]);
}
/**
* Prepare the item array for a "dislike"
*
* @param array $activity Activity array
*/
public static function dislikeItem($activity)
{
$item = [];
$item['verb'] = ACTIVITY_DISLIKE;
$item['parent-uri'] = $activity['object_id'];
$item['gravity'] = GRAVITY_ACTIVITY;
$item['object-type'] = ACTIVITY_OBJ_NOTE;
self::postItem($activity, $item);
}
/**
* Creates an item post
*
* @param array $activity Activity data
* @param array $item item array
*/
private static function postItem($activity, $item)
{
/// @todo What to do with $activity['context']?
if (($item['gravity'] != GRAVITY_PARENT) && !Item::exists(['uri' => $item['parent-uri']])) {
logger('Parent ' . $item['parent-uri'] . ' not found, message will be discarded.', LOGGER_DEBUG);
return;
}
$item['network'] = Protocol::ACTIVITYPUB;
$item['private'] = !in_array(0, $activity['receiver']);
$item['author-link'] = $activity['author'];
$item['author-id'] = Contact::getIdForURL($activity['author'], 0, true);
if (empty($activity['thread-completion'])) {
$item['owner-link'] = $activity['actor'];
$item['owner-id'] = Contact::getIdForURL($activity['actor'], 0, true);
} else {
logger('Ignoring actor because of thread completion.', LOGGER_DEBUG);
$item['owner-link'] = $item['author-link'];
$item['owner-id'] = $item['author-id'];
}
$item['uri'] = $activity['id'];
$item['created'] = $activity['published'];
$item['edited'] = $activity['updated'];
$item['guid'] = $activity['diaspora:guid'];
$item['title'] = HTML::toBBCode($activity['name']);
$item['content-warning'] = HTML::toBBCode($activity['summary']);
$item['body'] = self::convertMentions(HTML::toBBCode($activity['content']));
if (($activity['object_type'] == 'as:Video') && !empty($activity['alternate-url'])) {
$item['body'] .= "\n[video]" . $activity['alternate-url'] . '[/video]';
}
$item['location'] = $activity['location'];
if (!empty($item['latitude']) && !empty($item['longitude'])) {
$item['coord'] = $item['latitude'] . ' ' . $item['longitude'];
}
$item['tag'] = self::constructTagList($activity['tags'], $activity['sensitive']);
$item['app'] = $activity['generator'];
$item['plink'] = defaults($activity, 'alternate-url', $item['uri']);
$item['diaspora_signed_text'] = defaults($activity, 'diaspora:comment', '');
$item = self::constructAttachList($activity['attachments'], $item);
if (!empty($activity['source'])) {
$item['body'] = $activity['source'];
}
foreach ($activity['receiver'] as $receiver) {
$item['uid'] = $receiver;
$item['contact-id'] = Contact::getIdForURL($activity['author'], $receiver, true);
if (($receiver != 0) && empty($item['contact-id'])) {
$item['contact-id'] = Contact::getIdForURL($activity['author'], 0, true);
}
$item_id = Item::insert($item);
logger('Storing for user ' . $item['uid'] . ': ' . $item_id);
}
}
/**
* Fetches missing posts
*
* @param $url
* @param $child
*/
private static function fetchMissingActivity($url, $child)
{
if (Config::get('system', 'ostatus_full_threads')) {
return;
}
$object = ActivityPub::fetchContent($url);
if (empty($object)) {
logger('Activity ' . $url . ' was not fetchable, aborting.');
return;
}
$activity = [];
$activity['@context'] = $object['@context'];
unset($object['@context']);
$activity['id'] = $object['id'];
$activity['to'] = defaults($object, 'to', []);
$activity['cc'] = defaults($object, 'cc', []);
$activity['actor'] = $child['author'];
$activity['object'] = $object;
$activity['published'] = defaults($object, 'published', $child['published']);
$activity['type'] = 'Create';
$ldactivity = JsonLD::compact($activity);
$ldactivity['thread-completion'] = true;
ActivityPub\Receiver::processActivity($ldactivity);
logger('Activity ' . $url . ' had been fetched and processed.');
}
/**
* perform a "follow" request
*
* @param array $activity
*/
public static function followUser($activity)
{
$uid = User::getIdForURL($activity['object_id']);
if (empty($uid)) {
return;
}
$owner = User::getOwnerDataById($uid);
$cid = Contact::getIdForURL($activity['actor'], $uid);
if (!empty($cid)) {
self::switchContact($cid);
$contact = DBA::selectFirst('contact', [], ['id' => $cid, 'network' => Protocol::NATIVE_SUPPORT]);
} else {
$contact = false;
}
$item = ['author-id' => Contact::getIdForURL($activity['actor']),
'author-link' => $activity['actor']];
// Ensure that the contact has got the right network type
self::switchContact($item['author-id']);
Contact::addRelationship($owner, $contact, $item);
$cid = Contact::getIdForURL($activity['actor'], $uid);
if (empty($cid)) {
return;
}
DBA::update('contact', ['hub-verify' => $activity['id']], ['id' => $cid]);
logger('Follow user ' . $uid . ' from contact ' . $cid . ' with id ' . $activity['id']);
}
/**
* Update the given profile
*
* @param array $activity
*/
public static function updatePerson($activity)
{
if (empty($activity['object_id'])) {
return;
}
logger('Updating profile for ' . $activity['object_id'], LOGGER_DEBUG);
APContact::getByURL($activity['object_id'], true);
}
/**
* Delete the given profile
*
* @param array $activity
*/
public static function deletePerson($activity)
{
if (empty($activity['object_id']) || empty($activity['actor'])) {
logger('Empty object id or actor.', LOGGER_DEBUG);
return;
}
if ($activity['object_id'] != $activity['actor']) {
logger('Object id does not match actor.', LOGGER_DEBUG);
return;
}
$contacts = DBA::select('contact', ['id'], ['nurl' => normalise_link($activity['object_id'])]);
while ($contact = DBA::fetch($contacts)) {
Contact::remove($contact['id']);
}
DBA::close($contacts);
logger('Deleted contact ' . $activity['object_id'], LOGGER_DEBUG);
}
/**
* Accept a follow request
*
* @param array $activity
*/
public static function acceptFollowUser($activity)
{
$uid = User::getIdForURL($activity['object_actor']);
if (empty($uid)) {
return;
}
$owner = User::getOwnerDataById($uid);
$cid = Contact::getIdForURL($activity['actor'], $uid);
if (empty($cid)) {
logger('No contact found for ' . $activity['actor'], LOGGER_DEBUG);
return;
}
self::switchContact($cid);
$fields = ['pending' => false];
$contact = DBA::selectFirst('contact', ['rel'], ['id' => $cid]);
if ($contact['rel'] == Contact::FOLLOWER) {
$fields['rel'] = Contact::FRIEND;
}
$condition = ['id' => $cid];
DBA::update('contact', $fields, $condition);
logger('Accept contact request from contact ' . $cid . ' for user ' . $uid, LOGGER_DEBUG);
}
/**
* Reject a follow request
*
* @param array $activity
*/
public static function rejectFollowUser($activity)
{
$uid = User::getIdForURL($activity['object_actor']);
if (empty($uid)) {
return;
}
$owner = User::getOwnerDataById($uid);
$cid = Contact::getIdForURL($activity['actor'], $uid);
if (empty($cid)) {
logger('No contact found for ' . $activity['actor'], LOGGER_DEBUG);
return;
}
self::switchContact($cid);
if (DBA::exists('contact', ['id' => $cid, 'rel' => Contact::SHARING, 'pending' => true])) {
Contact::remove($cid);
logger('Rejected contact request from contact ' . $cid . ' for user ' . $uid . ' - contact had been removed.', LOGGER_DEBUG);
} else {
logger('Rejected contact request from contact ' . $cid . ' for user ' . $uid . '.', LOGGER_DEBUG);
}
}
/**
* Undo activity like "like" or "dislike"
*
* @param array $activity
*/
public static function undoActivity($activity)
{
if (empty($activity['object_id'])) {
return;
}
if (empty($activity['object_actor'])) {
return;
}
$author_id = Contact::getIdForURL($activity['object_actor']);
if (empty($author_id)) {
return;
}
Item::delete(['uri' => $activity['object_id'], 'author-id' => $author_id, 'gravity' => GRAVITY_ACTIVITY]);
}
/**
* Activity to remove a follower
*
* @param array $activity
*/
public static function undoFollowUser($activity)
{
$uid = User::getIdForURL($activity['object_object']);
if (empty($uid)) {
return;
}
$owner = User::getOwnerDataById($uid);
$cid = Contact::getIdForURL($activity['actor'], $uid);
if (empty($cid)) {
logger('No contact found for ' . $activity['actor'], LOGGER_DEBUG);
return;
}
self::switchContact($cid);
$contact = DBA::selectFirst('contact', [], ['id' => $cid]);
if (!DBA::isResult($contact)) {
return;
}
Contact::removeFollower($owner, $contact);
logger('Undo following request from contact ' . $cid . ' for user ' . $uid, LOGGER_DEBUG);
}
/**
* Switches a contact to AP if needed
*
* @param integer $cid Contact ID
*/
private static function switchContact($cid)
{
$contact = DBA::selectFirst('contact', ['network'], ['id' => $cid, 'network' => Protocol::NATIVE_SUPPORT]);
if (!DBA::isResult($contact) || ($contact['network'] == Protocol::ACTIVITYPUB)) {
return;
}
logger('Change existing contact ' . $cid . ' from ' . $contact['network'] . ' to ActivityPub.');
Contact::updateFromProbe($cid, Protocol::ACTIVITYPUB);
}
}

View file

@ -0,0 +1,735 @@
<?php
/**
* @file src/Protocol/ActivityPub/Receiver.php
*/
namespace Friendica\Protocol\ActivityPub;
use Friendica\Database\DBA;
use Friendica\Util\HTTPSignature;
use Friendica\Core\Protocol;
use Friendica\Model\Contact;
use Friendica\Model\APContact;
use Friendica\Model\Item;
use Friendica\Model\User;
use Friendica\Util\JsonLD;
use Friendica\Util\LDSignature;
use Friendica\Protocol\ActivityPub;
use Friendica\Model\Conversation;
use Friendica\Util\DateTimeFormat;
/**
* @brief ActivityPub Receiver Protocol class
*
* To-Do:
* - Update (Image, Video, Article, Note)
* - Event
* - Undo Announce
*
* Check what this is meant to do:
* - Add
* - Block
* - Flag
* - Remove
* - Undo Block
* - Undo Accept (Problem: This could invert a contact accept or an event accept)
*/
class Receiver
{
const PUBLIC_COLLECTION = 'as:Public';
const ACCOUNT_TYPES = ['as:Person', 'as:Organization', 'as:Service', 'as:Group', 'as:Application'];
const CONTENT_TYPES = ['as:Note', 'as:Article', 'as:Video', 'as:Image'];
const ACTIVITY_TYPES = ['as:Like', 'as:Dislike', 'as:Accept', 'as:Reject', 'as:TentativeAccept'];
/**
* Checks if the web request is done for the AP protocol
*
* @return is it AP?
*/
public static function isRequest()
{
return stristr(defaults($_SERVER, 'HTTP_ACCEPT', ''), 'application/activity+json') ||
stristr(defaults($_SERVER, 'HTTP_ACCEPT', ''), 'application/ld+json');
}
/**
* Checks incoming message from the inbox
*
* @param $body
* @param $header
* @param integer $uid User ID
*/
public static function processInbox($body, $header, $uid)
{
$http_signer = HTTPSignature::getSigner($body, $header);
if (empty($http_signer)) {
logger('Invalid HTTP signature, message will be discarded.', LOGGER_DEBUG);
return;
} else {
logger('HTTP signature is signed by ' . $http_signer, LOGGER_DEBUG);
}
$activity = json_decode($body, true);
if (empty($activity)) {
logger('Invalid body.', LOGGER_DEBUG);
return;
}
$ldactivity = JsonLD::compact($activity);
$actor = JsonLD::fetchElement($ldactivity, 'as:actor');
logger('Message for user ' . $uid . ' is from actor ' . $actor, LOGGER_DEBUG);
if (LDSignature::isSigned($activity)) {
$ld_signer = LDSignature::getSigner($activity);
if (empty($ld_signer)) {
logger('Invalid JSON-LD signature from ' . $actor, LOGGER_DEBUG);
}
if (!empty($ld_signer && ($actor == $http_signer))) {
logger('The HTTP and the JSON-LD signature belong to ' . $ld_signer, LOGGER_DEBUG);
$trust_source = true;
} elseif (!empty($ld_signer)) {
logger('JSON-LD signature is signed by ' . $ld_signer, LOGGER_DEBUG);
$trust_source = true;
} elseif ($actor == $http_signer) {
logger('Bad JSON-LD signature, but HTTP signer fits the actor.', LOGGER_DEBUG);
$trust_source = true;
} else {
logger('Invalid JSON-LD signature and the HTTP signer is different.', LOGGER_DEBUG);
$trust_source = false;
}
} elseif ($actor == $http_signer) {
logger('Trusting post without JSON-LD signature, The actor fits the HTTP signer.', LOGGER_DEBUG);
$trust_source = true;
} else {
logger('No JSON-LD signature, different actor.', LOGGER_DEBUG);
$trust_source = false;
}
self::processActivity($ldactivity, $body, $uid, $trust_source);
}
/**
* Fetches the object type for a given object id
*
* @param array $activity
* @param string $object_id Object ID of the the provided object
*
* @return string with object type
*/
private static function fetchObjectType($activity, $object_id)
{
$object_type = JsonLD::fetchElement($activity['as:object'], '@type');
if (!empty($object_type)) {
return $object_type;
}
if (Item::exists(['uri' => $object_id, 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT]])) {
// We just assume "note" since it doesn't make a difference for the further processing
return 'as:Note';
}
$profile = APContact::getByURL($object_id);
if (!empty($profile['type'])) {
return 'as:' . $profile['type'];
}
$data = ActivityPub::fetchContent($object_id);
if (!empty($data)) {
$object = JsonLD::compact($data);
$type = JsonLD::fetchElement($object, '@type');
if (!empty($type)) {
return $type;
}
}
return null;
}
/**
* Prepare the object array
*
* @param array $activity
* @param integer $uid User ID
* @param $trust_source
*
* @return array with object data
*/
private static function prepareObjectData($activity, $uid, &$trust_source)
{
$actor = JsonLD::fetchElement($activity, 'as:actor');
if (empty($actor)) {
logger('Empty actor', LOGGER_DEBUG);
return [];
}
$type = JsonLD::fetchElement($activity, '@type');
// Fetch all receivers from to, cc, bto and bcc
$receivers = self::getReceivers($activity, $actor);
// When it is a delivery to a personal inbox we add that user to the receivers
if (!empty($uid)) {
$owner = User::getOwnerDataById($uid);
$additional = ['uid:' . $uid => $uid];
$receivers = array_merge($receivers, $additional);
}
logger('Receivers: ' . json_encode($receivers), LOGGER_DEBUG);
$object_id = JsonLD::fetchElement($activity, 'as:object');
if (empty($object_id)) {
logger('No object found', LOGGER_DEBUG);
return [];
}
$object_type = self::fetchObjectType($activity, $object_id);
// Fetch the content only on activities where this matters
if (in_array($type, ['as:Create', 'as:Announce'])) {
if ($type == 'as:Announce') {
$trust_source = false;
}
$object_data = self::fetchObject($object_id, $activity['as:object'], $trust_source);
if (empty($object_data)) {
logger("Object data couldn't be processed", LOGGER_DEBUG);
return [];
}
// We had been able to retrieve the object data - so we can trust the source
$trust_source = true;
} elseif (in_array($type, ['as:Like', 'as:Dislike'])) {
// Create a mostly empty array out of the activity data (instead of the object).
// This way we later don't have to check for the existence of ech individual array element.
$object_data = self::processObject($activity);
$object_data['name'] = $type;
$object_data['author'] = JsonLD::fetchElement($activity, 'as:actor');
$object_data['object_id'] = $object_id;
$object_data['object_type'] = ''; // Since we don't fetch the object, we don't know the type
} else {
$object_data = [];
$object_data['id'] = JsonLD::fetchElement($activity, '@id');
$object_data['object_id'] = JsonLD::fetchElement($activity, 'as:object');
$object_data['object_actor'] = JsonLD::fetchElement($activity['as:object'], 'as:actor');
$object_data['object_object'] = JsonLD::fetchElement($activity['as:object'], 'as:object');
$object_data['object_type'] = JsonLD::fetchElement($activity['as:object'], '@type');
}
$object_data = self::addActivityFields($object_data, $activity);
if (empty($object_data['object_type'])) {
$object_data['object_type'] = $object_type;
}
$object_data['type'] = $type;
$object_data['actor'] = $actor;
$object_data['receiver'] = array_merge(defaults($object_data, 'receiver', []), $receivers);
logger('Processing ' . $object_data['type'] . ' ' . $object_data['object_type'] . ' ' . $object_data['id'], LOGGER_DEBUG);
return $object_data;
}
/**
* Store the unprocessed data into the conversation table
* This has to be done outside the regular function,
* since we store everything - not only item posts.
*
* @param array $activity Array with activity data
* @param string $body The raw message
*/
private static function storeConversation($activity, $body)
{
if (empty($body) || empty($activity['id'])) {
return;
}
$conversation = [
'protocol' => Conversation::PARCEL_ACTIVITYPUB,
'item-uri' => $activity['id'],
'reply-to-uri' => defaults($activity, 'reply-to-id', ''),
'conversation-href' => defaults($activity, 'context', ''),
'conversation-uri' => defaults($activity, 'conversation', ''),
'source' => $body,
'received' => DateTimeFormat::utcNow()];
DBA::insert('conversation', $conversation, true);
}
/**
* Processes the activity object
*
* @param array $activity Array with activity data
* @param string $body
* @param integer $uid User ID
* @param boolean $trust_source Do we trust the source?
*/
public static function processActivity($activity, $body = '', $uid = null, $trust_source = false)
{
$type = JsonLD::fetchElement($activity, '@type');
if (!$type) {
logger('Empty type', LOGGER_DEBUG);
return;
}
if (!JsonLD::fetchElement($activity, 'as:object')) {
logger('Empty object', LOGGER_DEBUG);
return;
}
if (!JsonLD::fetchElement($activity, 'as:actor')) {
logger('Empty actor', LOGGER_DEBUG);
return;
}
// $trust_source is called by reference and is set to true if the content was retrieved successfully
$object_data = self::prepareObjectData($activity, $uid, $trust_source);
if (empty($object_data)) {
logger('No object data found', LOGGER_DEBUG);
return;
}
if (!$trust_source) {
logger('No trust for activity type "' . $type . '", so we quit now.', LOGGER_DEBUG);
return;
}
self::storeConversation($object_data, $body);
// Internal flag for thread completion. See Processor.php
if (!empty($activity['thread-completion'])) {
$object_data['thread-completion'] = $activity['thread-completion'];
}
switch ($type) {
case 'as:Create':
case 'as:Announce':
ActivityPub\Processor::createItem($object_data);
break;
case 'as:Like':
ActivityPub\Processor::likeItem($object_data);
break;
case 'as:Dislike':
ActivityPub\Processor::dislikeItem($object_data);
break;
case 'as:Update':
if (in_array($object_data['object_type'], self::CONTENT_TYPES)) {
/// @todo
} elseif (in_array($object_data['object_type'], self::ACCOUNT_TYPES)) {
ActivityPub\Processor::updatePerson($object_data, $body);
}
break;
case 'as:Delete':
if ($object_data['object_type'] == 'as:Tombstone') {
ActivityPub\Processor::deleteItem($object_data, $body);
} elseif (in_array($object_data['object_type'], self::ACCOUNT_TYPES)) {
ActivityPub\Processor::deletePerson($object_data, $body);
}
break;
case 'as:Follow':
ActivityPub\Processor::followUser($object_data);
break;
case 'as:Accept':
if ($object_data['object_type'] == 'as:Follow') {
ActivityPub\Processor::acceptFollowUser($object_data);
}
break;
case 'as:Reject':
if ($object_data['object_type'] == 'as:Follow') {
ActivityPub\Processor::rejectFollowUser($object_data);
}
break;
case 'as:Undo':
if ($object_data['object_type'] == 'as:Follow') {
ActivityPub\Processor::undoFollowUser($object_data);
} elseif (in_array($object_data['object_type'], self::ACTIVITY_TYPES)) {
ActivityPub\Processor::undoActivity($object_data);
}
break;
default:
logger('Unknown activity: ' . $type, LOGGER_DEBUG);
break;
}
}
/**
* Fetch the receiver list from an activity array
*
* @param array $activity
* @param string $actor
*
* @return array with receivers (user id)
*/
private static function getReceivers($activity, $actor)
{
$receivers = [];
// When it is an answer, we inherite the receivers from the parent
$replyto = JsonLD::fetchElement($activity, 'as:inReplyTo');
if (!empty($replyto)) {
$parents = Item::select(['uid'], ['uri' => $replyto]);
while ($parent = Item::fetch($parents)) {
$receivers['uid:' . $parent['uid']] = $parent['uid'];
}
}
if (!empty($actor)) {
$profile = APContact::getByURL($actor);
$followers = defaults($profile, 'followers', '');
logger('Actor: ' . $actor . ' - Followers: ' . $followers, LOGGER_DEBUG);
} else {
logger('Empty actor', LOGGER_DEBUG);
$followers = '';
}
foreach (['as:to', 'as:cc', 'as:bto', 'as:bcc'] as $element) {
$receiver_list = JsonLD::fetchElementArray($activity, $element);
if (empty($receiver_list)) {
continue;
}
foreach ($receiver_list as $receiver) {
if ($receiver == self::PUBLIC_COLLECTION) {
$receivers['uid:0'] = 0;
}
if (($receiver == self::PUBLIC_COLLECTION) && !empty($actor)) {
// This will most likely catch all OStatus connections to Mastodon
$condition = ['alias' => [$actor, normalise_link($actor)], 'rel' => [Contact::SHARING, Contact::FRIEND]
, 'archive' => false, 'pending' => false];
$contacts = DBA::select('contact', ['uid'], $condition);
while ($contact = DBA::fetch($contacts)) {
if ($contact['uid'] != 0) {
$receivers['uid:' . $contact['uid']] = $contact['uid'];
}
}
DBA::close($contacts);
}
if (in_array($receiver, [$followers, self::PUBLIC_COLLECTION]) && !empty($actor)) {
$condition = ['nurl' => normalise_link($actor), 'rel' => [Contact::SHARING, Contact::FRIEND],
'network' => Protocol::ACTIVITYPUB, 'archive' => false, 'pending' => false];
$contacts = DBA::select('contact', ['uid'], $condition);
while ($contact = DBA::fetch($contacts)) {
if ($contact['uid'] != 0) {
$receivers['uid:' . $contact['uid']] = $contact['uid'];
}
}
DBA::close($contacts);
continue;
}
$condition = ['self' => true, 'nurl' => normalise_link($receiver)];
$contact = DBA::selectFirst('contact', ['uid'], $condition);
if (!DBA::isResult($contact)) {
continue;
}
$receivers['uid:' . $contact['uid']] = $contact['uid'];
}
}
self::switchContacts($receivers, $actor);
return $receivers;
}
/**
* Switches existing contacts to ActivityPub
*
* @param integer $cid Contact ID
* @param integer $uid User ID
* @param string $url Profile URL
*/
public static function switchContact($cid, $uid, $url)
{
$profile = ActivityPub::probeProfile($url);
if (empty($profile)) {
return;
}
logger('Switch contact ' . $cid . ' (' . $profile['url'] . ') for user ' . $uid . ' to ActivityPub');
$photo = defaults($profile, 'photo', null);
unset($profile['photo']);
unset($profile['baseurl']);
$profile['nurl'] = normalise_link($profile['url']);
DBA::update('contact', $profile, ['id' => $cid]);
Contact::updateAvatar($photo, $uid, $cid);
// Send a new follow request to be sure that the connection still exists
if (($uid != 0) && DBA::exists('contact', ['id' => $cid, 'rel' => [Contact::SHARING, Contact::FRIEND]])) {
ActivityPub\Transmitter::sendActivity('Follow', $profile['url'], $uid);
logger('Send a new follow request to ' . $profile['url'] . ' for user ' . $uid, LOGGER_DEBUG);
}
}
/**
*
*
* @param $receivers
* @param $actor
*/
private static function switchContacts($receivers, $actor)
{
if (empty($actor)) {
return;
}
foreach ($receivers as $receiver) {
$contact = DBA::selectFirst('contact', ['id'], ['uid' => $receiver, 'network' => Protocol::OSTATUS, 'nurl' => normalise_link($actor)]);
if (DBA::isResult($contact)) {
self::switchContact($contact['id'], $receiver, $actor);
}
$contact = DBA::selectFirst('contact', ['id'], ['uid' => $receiver, 'network' => Protocol::OSTATUS, 'alias' => [normalise_link($actor), $actor]]);
if (DBA::isResult($contact)) {
self::switchContact($contact['id'], $receiver, $actor);
}
}
}
/**
*
*
* @param $object_data
* @param array $activity
*
* @return
*/
private static function addActivityFields($object_data, $activity)
{
if (!empty($activity['published']) && empty($object_data['published'])) {
$object_data['published'] = JsonLD::fetchElement($activity, 'as:published', '@value');
}
if (!empty($activity['diaspora:guid']) && empty($object_data['diaspora:guid'])) {
$object_data['diaspora:guid'] = JsonLD::fetchElement($activity, 'diaspora:guid');
}
$object_data['service'] = JsonLD::fetchElement($activity, 'as:instrument', 'as:name', '@type', 'as:Service');
return $object_data;
}
/**
* Fetches the object data from external ressources if needed
*
* @param string $object_id Object ID of the the provided object
* @param array $object The provided object array
* @param boolean $trust_source Do we trust the provided object?
*
* @return array with trusted and valid object data
*/
private static function fetchObject($object_id, $object = [], $trust_source = false)
{
// By fetching the type we check if the object is complete.
$type = JsonLD::fetchElement($object, '@type');
if (!$trust_source || empty($type)) {
$data = ActivityPub::fetchContent($object_id);
if (!empty($data)) {
$object = JsonLD::compact($data);
logger('Fetched content for ' . $object_id, LOGGER_DEBUG);
} else {
logger('Empty content for ' . $object_id . ', check if content is available locally.', LOGGER_DEBUG);
$item = Item::selectFirst([], ['uri' => $object_id]);
if (!DBA::isResult($item)) {
logger('Object with url ' . $object_id . ' was not found locally.', LOGGER_DEBUG);
return false;
}
logger('Using already stored item for url ' . $object_id, LOGGER_DEBUG);
$data = ActivityPub\Transmitter::createNote($item);
$object = JsonLD::compact($data);
}
} else {
logger('Using original object for url ' . $object_id, LOGGER_DEBUG);
}
$type = JsonLD::fetchElement($object, '@type');
if (empty($type)) {
logger('Empty type', LOGGER_DEBUG);
return false;
}
if (in_array($type, self::CONTENT_TYPES)) {
return self::processObject($object);
}
if ($type == 'as:Announce') {
$object_id = JsonLD::fetchElement($object, 'object');
if (empty($object_id)) {
return false;
}
return self::fetchObject($object_id);
}
logger('Unhandled object type: ' . $type, LOGGER_DEBUG);
}
/**
* Convert tags from JSON-LD format into a simplified format
*
* @param array $tags Tags in JSON-LD format
*
* @return array with tags in a simplified format
*/
private static function processTags($tags)
{
$taglist = [];
if (empty($tags)) {
return [];
}
foreach ($tags as $tag) {
if (empty($tag)) {
continue;
}
$taglist[] = ['type' => str_replace('as:', '', JsonLD::fetchElement($tag, '@type')),
'href' => JsonLD::fetchElement($tag, 'as:href'),
'name' => JsonLD::fetchElement($tag, 'as:name')];
}
return $taglist;
}
/**
* Convert attachments from JSON-LD format into a simplified format
*
* @param array $attachments Attachments in JSON-LD format
*
* @return array with attachmants in a simplified format
*/
private static function processAttachments($attachments)
{
$attachlist = [];
if (empty($attachments)) {
return [];
}
foreach ($attachments as $attachment) {
if (empty($attachment)) {
continue;
}
$attachlist[] = ['type' => str_replace('as:', '', JsonLD::fetchElement($attachment, '@type')),
'mediaType' => JsonLD::fetchElement($attachment, 'as:mediaType'),
'name' => JsonLD::fetchElement($attachment, 'as:name'),
'url' => JsonLD::fetchElement($attachment, 'as:url')];
}
return $attachlist;
}
/**
* Fetches data from the object part of an activity
*
* @param array $object
*
* @return array
*/
private static function processObject($object)
{
if (!JsonLD::fetchElement($object, '@id')) {
return false;
}
$object_data = [];
$object_data['object_type'] = JsonLD::fetchElement($object, '@type');
$object_data['id'] = JsonLD::fetchElement($object, '@id');
$object_data['reply-to-id'] = JsonLD::fetchElement($object, 'as:inReplyTo');
if (empty($object_data['reply-to-id'])) {
$object_data['reply-to-id'] = $object_data['id'];
}
$object_data['published'] = JsonLD::fetchElement($object, 'as:published', '@value');
$object_data['updated'] = JsonLD::fetchElement($object, 'as:updated', '@value');
if (empty($object_data['updated'])) {
$object_data['updated'] = $object_data['published'];
}
if (empty($object_data['published']) && !empty($object_data['updated'])) {
$object_data['published'] = $object_data['updated'];
}
$actor = JsonLD::fetchElement($object, 'as:attributedTo');
if (empty($actor)) {
$actor = JsonLD::fetchElement($object, 'as:actor');
}
$object_data['diaspora:guid'] = JsonLD::fetchElement($object, 'diaspora:guid');
$object_data['diaspora:comment'] = JsonLD::fetchElement($object, 'diaspora:comment');
$object_data['actor'] = $object_data['author'] = $actor;
$object_data['context'] = JsonLD::fetchElement($object, 'as:context');
$object_data['conversation'] = JsonLD::fetchElement($object, 'ostatus:conversation');
$object_data['sensitive'] = JsonLD::fetchElement($object, 'as:sensitive');
$object_data['name'] = JsonLD::fetchElement($object, 'as:name');
$object_data['summary'] = JsonLD::fetchElement($object, 'as:summary');
$object_data['content'] = JsonLD::fetchElement($object, 'as:content');
$object_data['source'] = JsonLD::fetchElement($object, 'as:source', 'as:content', 'as:mediaType', 'text/bbcode');
$object_data['location'] = JsonLD::fetchElement($object, 'as:location', 'as:name', '@type', 'as:Place');
$object_data['latitude'] = JsonLD::fetchElement($object, 'as:location', 'as:latitude', '@type', 'as:Place');
$object_data['latitude'] = JsonLD::fetchElement($object_data, 'latitude', '@value');
$object_data['longitude'] = JsonLD::fetchElement($object, 'as:location', 'as:longitude', '@type', 'as:Place');
$object_data['longitude'] = JsonLD::fetchElement($object_data, 'longitude', '@value');
$object_data['attachments'] = self::processAttachments(JsonLD::fetchElementArray($object, 'as:attachment'));
$object_data['tags'] = self::processTags(JsonLD::fetchElementArray($object, 'as:tag'));
$object_data['generator'] = JsonLD::fetchElement($object, 'as:generator', 'as:name', '@type', 'as:Application');
$object_data['alternate-url'] = JsonLD::fetchElement($object, 'as:url');
// Special treatment for Hubzilla links
if (is_array($object_data['alternate-url'])) {
$object_data['alternate-url'] = JsonLD::fetchElement($object_data['alternate-url'], 'as:href');
if (!is_string($object_data['alternate-url'])) {
$object_data['alternate-url'] = JsonLD::fetchElement($object['as:url'], 'as:href');
}
}
$object_data['receiver'] = self::getReceivers($object, $object_data['actor']);
// Common object data:
// Unhandled
// @context, type, actor, signature, mediaType, duration, replies, icon
// Also missing: (Defined in the standard, but currently unused)
// audience, preview, endTime, startTime, image
// Data in Notes:
// Unhandled
// contentMap, announcement_count, announcements, context_id, likes, like_count
// inReplyToStatusId, shares, quoteUrl, statusnetConversationId
// Data in video:
// To-Do?
// category, licence, language, commentsEnabled
// Unhandled
// views, waitTranscoding, state, support, subtitleLanguage
// likes, dislikes, shares, comments
return $object_data;
}
}

View file

@ -0,0 +1,1089 @@
<?php
/**
* @file src/Protocol/ActivityPub/Transmitter.php
*/
namespace Friendica\Protocol\ActivityPub;
use Friendica\BaseObject;
use Friendica\Database\DBA;
use Friendica\Core\System;
use Friendica\Util\HTTPSignature;
use Friendica\Core\Protocol;
use Friendica\Model\Conversation;
use Friendica\Model\Contact;
use Friendica\Model\APContact;
use Friendica\Model\Item;
use Friendica\Model\Term;
use Friendica\Model\User;
use Friendica\Util\DateTimeFormat;
use Friendica\Content\Text\BBCode;
use Friendica\Util\JsonLD;
use Friendica\Util\LDSignature;
use Friendica\Model\Profile;
use Friendica\Core\Config;
use Friendica\Object\Image;
use Friendica\Protocol\ActivityPub;
use Friendica\Protocol\Diaspora;
use Friendica\Core\Cache;
use Friendica\Util\Map;
require_once 'include/api.php';
/**
* @brief ActivityPub Transmitter Protocol class
*
* To-Do:
*
* Missing object types:
* - Event
*
* Complicated object types:
* - Undo Announce
*
* General:
* - Queueing unsucessful deliveries
*/
class Transmitter
{
/**
* collects the lost of followers of the given owner
*
* @param array $owner Owner array
* @param integer $page Page number
*
* @return array of owners
*/
public static function getFollowers($owner, $page = null)
{
$condition = ['rel' => [Contact::FOLLOWER, Contact::FRIEND], 'network' => Protocol::NATIVE_SUPPORT, 'uid' => $owner['uid'],
'self' => false, 'hidden' => false, 'archive' => false, 'pending' => false];
$count = DBA::count('contact', $condition);
$data = ['@context' => ActivityPub::CONTEXT];
$data['id'] = System::baseUrl() . '/followers/' . $owner['nickname'];
$data['type'] = 'OrderedCollection';
$data['totalItems'] = $count;
// When we hide our friends we will only show the pure number but don't allow more.
$profile = Profile::getByUID($owner['uid']);
if (!empty($profile['hide-friends'])) {
return $data;
}
if (empty($page)) {
$data['first'] = System::baseUrl() . '/followers/' . $owner['nickname'] . '?page=1';
} else {
$list = [];
$contacts = DBA::select('contact', ['url'], $condition, ['limit' => [($page - 1) * 100, 100]]);
while ($contact = DBA::fetch($contacts)) {
$list[] = $contact['url'];
}
if (!empty($list)) {
$data['next'] = System::baseUrl() . '/followers/' . $owner['nickname'] . '?page=' . ($page + 1);
}
$data['partOf'] = System::baseUrl() . '/followers/' . $owner['nickname'];
$data['orderedItems'] = $list;
}
return $data;
}
/**
* Create list of following contacts
*
* @param array $owner Owner array
* @param integer $page Page numbe
*
* @return array of following contacts
*/
public static function getFollowing($owner, $page = null)
{
$condition = ['rel' => [Contact::SHARING, Contact::FRIEND], 'network' => Protocol::NATIVE_SUPPORT, 'uid' => $owner['uid'],
'self' => false, 'hidden' => false, 'archive' => false, 'pending' => false];
$count = DBA::count('contact', $condition);
$data = ['@context' => ActivityPub::CONTEXT];
$data['id'] = System::baseUrl() . '/following/' . $owner['nickname'];
$data['type'] = 'OrderedCollection';
$data['totalItems'] = $count;
// When we hide our friends we will only show the pure number but don't allow more.
$profile = Profile::getByUID($owner['uid']);
if (!empty($profile['hide-friends'])) {
return $data;
}
if (empty($page)) {
$data['first'] = System::baseUrl() . '/following/' . $owner['nickname'] . '?page=1';
} else {
$list = [];
$contacts = DBA::select('contact', ['url'], $condition, ['limit' => [($page - 1) * 100, 100]]);
while ($contact = DBA::fetch($contacts)) {
$list[] = $contact['url'];
}
if (!empty($list)) {
$data['next'] = System::baseUrl() . '/following/' . $owner['nickname'] . '?page=' . ($page + 1);
}
$data['partOf'] = System::baseUrl() . '/following/' . $owner['nickname'];
$data['orderedItems'] = $list;
}
return $data;
}
/**
* Public posts for the given owner
*
* @param array $owner Owner array
* @param integer $page Page numbe
*
* @return array of posts
*/
public static function getOutbox($owner, $page = null)
{
$public_contact = Contact::getIdForURL($owner['url'], 0, true);
$condition = ['uid' => 0, 'contact-id' => $public_contact, 'author-id' => $public_contact,
'private' => false, 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT],
'deleted' => false, 'visible' => true];
$count = DBA::count('item', $condition);
$data = ['@context' => ActivityPub::CONTEXT];
$data['id'] = System::baseUrl() . '/outbox/' . $owner['nickname'];
$data['type'] = 'OrderedCollection';
$data['totalItems'] = $count;
if (empty($page)) {
$data['first'] = System::baseUrl() . '/outbox/' . $owner['nickname'] . '?page=1';
} else {
$list = [];
$condition['parent-network'] = Protocol::NATIVE_SUPPORT;
$items = Item::select(['id'], $condition, ['limit' => [($page - 1) * 20, 20], 'order' => ['created' => true]]);
while ($item = Item::fetch($items)) {
$object = self::createObjectFromItemID($item['id']);
unset($object['@context']);
$list[] = $object;
}
if (!empty($list)) {
$data['next'] = System::baseUrl() . '/outbox/' . $owner['nickname'] . '?page=' . ($page + 1);
}
$data['partOf'] = System::baseUrl() . '/outbox/' . $owner['nickname'];
$data['orderedItems'] = $list;
}
return $data;
}
/**
* Return the ActivityPub profile of the given user
*
* @param integer $uid User ID
* @return array with profile data
*/
public static function getProfile($uid)
{
$condition = ['uid' => $uid, 'blocked' => false, 'account_expired' => false,
'account_removed' => false, 'verified' => true];
$fields = ['guid', 'nickname', 'pubkey', 'account-type', 'page-flags'];
$user = DBA::selectFirst('user', $fields, $condition);
if (!DBA::isResult($user)) {
return [];
}
$fields = ['locality', 'region', 'country-name'];
$profile = DBA::selectFirst('profile', $fields, ['uid' => $uid, 'is-default' => true]);
if (!DBA::isResult($profile)) {
return [];
}
$fields = ['name', 'url', 'location', 'about', 'avatar', 'photo'];
$contact = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]);
if (!DBA::isResult($contact)) {
return [];
}
// On old installations and never changed contacts this might not be filled
if (empty($contact['avatar'])) {
$contact['avatar'] = $contact['photo'];
}
$data = ['@context' => ActivityPub::CONTEXT];
$data['id'] = $contact['url'];
$data['diaspora:guid'] = $user['guid'];
$data['type'] = ActivityPub::ACCOUNT_TYPES[$user['account-type']];
$data['following'] = System::baseUrl() . '/following/' . $user['nickname'];
$data['followers'] = System::baseUrl() . '/followers/' . $user['nickname'];
$data['inbox'] = System::baseUrl() . '/inbox/' . $user['nickname'];
$data['outbox'] = System::baseUrl() . '/outbox/' . $user['nickname'];
$data['preferredUsername'] = $user['nickname'];
$data['name'] = $contact['name'];
$data['vcard:hasAddress'] = ['@type' => 'vcard:Home', 'vcard:country-name' => $profile['country-name'],
'vcard:region' => $profile['region'], 'vcard:locality' => $profile['locality']];
$data['summary'] = $contact['about'];
$data['url'] = $contact['url'];
$data['manuallyApprovesFollowers'] = in_array($user['page-flags'], [Contact::PAGE_NORMAL, Contact::PAGE_PRVGROUP]);
$data['publicKey'] = ['id' => $contact['url'] . '#main-key',
'owner' => $contact['url'],
'publicKeyPem' => $user['pubkey']];
$data['endpoints'] = ['sharedInbox' => System::baseUrl() . '/inbox'];
$data['icon'] = ['type' => 'Image',
'url' => $contact['avatar']];
// tags: https://kitty.town/@inmysocks/100656097926961126.json
return $data;
}
/**
* Returns an array with permissions of a given item array
*
* @param array $item
*
* @return array with permissions
*/
private static function fetchPermissionBlockFromConversation($item)
{
if (empty($item['thr-parent'])) {
return [];
}
$condition = ['item-uri' => $item['thr-parent'], 'protocol' => Conversation::PARCEL_ACTIVITYPUB];
$conversation = DBA::selectFirst('conversation', ['source'], $condition);
if (!DBA::isResult($conversation)) {
return [];
}
$activity = json_decode($conversation['source'], true);
$actor = JsonLD::fetchElement($activity, 'actor', 'id');
$profile = APContact::getByURL($actor);
$item_profile = APContact::getByURL($item['author-link']);
$exclude[] = $item['author-link'];
if ($item['gravity'] == GRAVITY_PARENT) {
$exclude[] = $item['owner-link'];
}
$permissions['to'][] = $actor;
foreach (['to', 'cc', 'bto', 'bcc'] as $element) {
if (empty($activity[$element])) {
continue;
}
if (is_string($activity[$element])) {
$activity[$element] = [$activity[$element]];
}
foreach ($activity[$element] as $receiver) {
if ($receiver == $profile['followers'] && !empty($item_profile['followers'])) {
$receiver = $item_profile['followers'];
}
if (!in_array($receiver, $exclude)) {
$permissions[$element][] = $receiver;
}
}
}
return $permissions;
}
/**
* Creates an array of permissions from an item thread
*
* @param array $item
*
* @return array with permission data
*/
private static function createPermissionBlockForItem($item)
{
$data = ['to' => [], 'cc' => []];
$data = array_merge($data, self::fetchPermissionBlockFromConversation($item));
$actor_profile = APContact::getByURL($item['author-link']);
$terms = Term::tagArrayFromItemId($item['id'], TERM_MENTION);
$contacts[$item['author-link']] = $item['author-link'];
if (!$item['private']) {
$data['to'][] = ActivityPub::PUBLIC_COLLECTION;
if (!empty($actor_profile['followers'])) {
$data['cc'][] = $actor_profile['followers'];
}
foreach ($terms as $term) {
$profile = APContact::getByURL($term['url'], false);
if (!empty($profile) && empty($contacts[$profile['url']])) {
$data['to'][] = $profile['url'];
$contacts[$profile['url']] = $profile['url'];
if (($key = array_search($profile['url'], $data['cc'])) !== false) {
unset($data['cc'][$key]);
}
}
}
} else {
$receiver_list = Item::enumeratePermissions($item);
$mentioned = [];
foreach ($terms as $term) {
$cid = Contact::getIdForURL($term['url'], $item['uid']);
if (!empty($cid) && in_array($cid, $receiver_list)) {
$contact = DBA::selectFirst('contact', ['url'], ['id' => $cid, 'network' => Protocol::ACTIVITYPUB]);
$data['to'][] = $contact['url'];
$contacts[$contact['url']] = $contact['url'];
if (($key = array_search($profile['url'], $data['cc'])) !== false) {
unset($data['cc'][$key]);
}
}
}
foreach ($receiver_list as $receiver) {
$contact = DBA::selectFirst('contact', ['url'], ['id' => $receiver, 'network' => Protocol::ACTIVITYPUB]);
if (DBA::isResult($contact) && empty($contacts[$contact['url']])) {
$data['cc'][] = $contact['url'];
$contacts[$contact['url']] = $contact['url'];
}
}
}
$parents = Item::select(['id', 'author-link', 'owner-link', 'gravity', 'uri'], ['parent' => $item['parent']]);
while ($parent = Item::fetch($parents)) {
// Don't include data from future posts
if ($parent['id'] >= $item['id']) {
continue;
}
$profile = APContact::getByURL($parent['author-link'], false);
if (!empty($profile) && ($parent['uri'] == $item['thr-parent'])) {
$data['to'][] = $profile['url'];
$contacts[$profile['url']] = $profile['url'];
if (($key = array_search($profile['url'], $data['cc'])) !== false) {
unset($data['cc'][$key]);
}
}
if (!empty($profile) && empty($contacts[$profile['url']])) {
$data['cc'][] = $profile['url'];
$contacts[$profile['url']] = $profile['url'];
}
if ($item['gravity'] != GRAVITY_PARENT) {
continue;
}
$profile = APContact::getByURL($parent['owner-link'], false);
if (!empty($profile) && empty($contacts[$profile['url']])) {
$data['cc'][] = $profile['url'];
$contacts[$profile['url']] = $profile['url'];
}
}
DBA::close($parents);
if (($key = array_search($item['author-link'], $data['to'])) !== false) {
unset($data['to'][$key]);
}
if (($key = array_search($item['author-link'], $data['cc'])) !== false) {
unset($data['cc'][$key]);
}
return ['to' => array_values(array_unique($data['to'])), 'cc' => array_values(array_unique($data['cc']))];
}
/**
* Fetches a list of inboxes of followers of a given user
*
* @param integer $uid User ID
*
* @return array of follower inboxes
*/
public static function fetchTargetInboxesforUser($uid)
{
$inboxes = [];
$condition = ['uid' => $uid, 'network' => Protocol::ACTIVITYPUB, 'archive' => false, 'pending' => false];
if (!empty($uid)) {
$condition['rel'] = [Contact::FOLLOWER, Contact::FRIEND];
}
$contacts = DBA::select('contact', ['notify', 'batch'], $condition);
while ($contact = DBA::fetch($contacts)) {
$contact = defaults($contact, 'batch', $contact['notify']);
$inboxes[$contact] = $contact;
}
DBA::close($contacts);
return $inboxes;
}
/**
* Fetches an array of inboxes for the given item and user
*
* @param array $item
* @param integer $uid User ID
*
* @return array with inboxes
*/
public static function fetchTargetInboxes($item, $uid)
{
$permissions = self::createPermissionBlockForItem($item);
if (empty($permissions)) {
return [];
}
$inboxes = [];
if ($item['gravity'] == GRAVITY_ACTIVITY) {
$item_profile = APContact::getByURL($item['author-link']);
} else {
$item_profile = APContact::getByURL($item['owner-link']);
}
foreach (['to', 'cc', 'bto', 'bcc'] as $element) {
if (empty($permissions[$element])) {
continue;
}
foreach ($permissions[$element] as $receiver) {
if ($receiver == $item_profile['followers']) {
$inboxes = self::fetchTargetInboxesforUser($uid);
} else {
$profile = APContact::getByURL($receiver);
if (!empty($profile)) {
$target = defaults($profile, 'sharedinbox', $profile['inbox']);
$inboxes[$target] = $target;
}
}
}
}
return $inboxes;
}
/**
* Returns the activity type of a given item
*
* @param array $item
*
* @return string with activity type
*/
private static function getTypeOfItem($item)
{
if (!empty(Diaspora::isReshare($item['body'], false))) {
$type = 'Announce';
} elseif ($item['verb'] == ACTIVITY_POST) {
if ($item['created'] == $item['edited']) {
$type = 'Create';
} else {
$type = 'Update';
}
} elseif ($item['verb'] == ACTIVITY_LIKE) {
$type = 'Like';
} elseif ($item['verb'] == ACTIVITY_DISLIKE) {
$type = 'Dislike';
} elseif ($item['verb'] == ACTIVITY_ATTEND) {
$type = 'Accept';
} elseif ($item['verb'] == ACTIVITY_ATTENDNO) {
$type = 'Reject';
} elseif ($item['verb'] == ACTIVITY_ATTENDMAYBE) {
$type = 'TentativeAccept';
} else {
$type = '';
}
return $type;
}
/**
* Creates the activity or fetches it from the cache
*
* @param integer $item_id
*
* @return array with the activity
*/
public static function createCachedActivityFromItem($item_id)
{
$cachekey = 'APDelivery:createActivity:' . $item_id;
$data = Cache::get($cachekey);
if (!is_null($data)) {
return $data;
}
$data = ActivityPub\Transmitter::createActivityFromItem($item_id);
Cache::set($cachekey, $data, CACHE_QUARTER_HOUR);
return $data;
}
/**
* Creates an activity array for a given item id
*
* @param integer $item_id
* @param boolean $object_mode Is the activity item is used inside another object?
*
* @return array of activity
*/
public static function createActivityFromItem($item_id, $object_mode = false)
{
$item = Item::selectFirst([], ['id' => $item_id, 'parent-network' => Protocol::NATIVE_SUPPORT]);
if (!DBA::isResult($item)) {
return false;
}
$condition = ['item-uri' => $item['uri'], 'protocol' => Conversation::PARCEL_ACTIVITYPUB];
$conversation = DBA::selectFirst('conversation', ['source'], $condition);
if (DBA::isResult($conversation)) {
$data = json_decode($conversation['source']);
if (!empty($data)) {
return $data;
}
}
$type = self::getTypeOfItem($item);
if (!$object_mode) {
$data = ['@context' => ActivityPub::CONTEXT];
if ($item['deleted'] && ($item['gravity'] == GRAVITY_ACTIVITY)) {
$type = 'Undo';
} elseif ($item['deleted']) {
$type = 'Delete';
}
} else {
$data = [];
}
$data['id'] = $item['uri'] . '#' . $type;
$data['type'] = $type;
$data['actor'] = $item['owner-link'];
$data['published'] = DateTimeFormat::utc($item['created'] . '+00:00', DateTimeFormat::ATOM);
$data['instrument'] = ['type' => 'Service', 'name' => BaseObject::getApp()->getUserAgent()];
$data = array_merge($data, self::createPermissionBlockForItem($item));
if (in_array($data['type'], ['Create', 'Update', 'Delete'])) {
$data['object'] = self::createNote($item);
} elseif ($data['type'] == 'Announce') {
$data['object'] = self::createAnnounce($item);
} elseif ($data['type'] == 'Undo') {
$data['object'] = self::createActivityFromItem($item_id, true);
} else {
$data['diaspora:guid'] = $item['guid'];
$data['object'] = $item['thr-parent'];
}
$owner = User::getOwnerDataById($item['uid']);
if (!$object_mode) {
return LDSignature::sign($data, $owner);
} else {
return $data;
}
/// @todo Create "conversation" entry
}
/**
* Creates an object array for a given item id
*
* @param integer $item_id
*
* @return array with the object data
*/
public static function createObjectFromItemID($item_id)
{
$item = Item::selectFirst([], ['id' => $item_id, 'parent-network' => Protocol::NATIVE_SUPPORT]);
if (!DBA::isResult($item)) {
return false;
}
$data = ['@context' => ActivityPub::CONTEXT];
$data = array_merge($data, self::createNote($item));
return $data;
}
/**
* Creates a location entry for a given item array
*
* @param array $item
*
* @return array with location array
*/
private static function createLocation($item)
{
$location = ['type' => 'Place'];
if (!empty($item['location'])) {
$location['name'] = $item['location'];
}
$coord = [];
if (empty($item['coord'])) {
$coord = Map::getCoordinates($item['location']);
} else {
$coords = explode(' ', $item['coord']);
if (count($coords) == 2) {
$coord = ['lat' => $coords[0], 'lon' => $coords[1]];
}
}
if (!empty($coord['lat']) && !empty($coord['lon'])) {
$location['latitude'] = $coord['lat'];
$location['longitude'] = $coord['lon'];
}
return $location;
}
/**
* Returns a tag array for a given item array
*
* @param array $item
*
* @return array of tags
*/
private static function createTagList($item)
{
$tags = [];
$terms = Term::tagArrayFromItemId($item['id']);
foreach ($terms as $term) {
if ($term['type'] == TERM_HASHTAG) {
$tags[] = ['type' => 'Hashtag', 'href' => $term['url'], 'name' => '#' . $term['term']];
} elseif ($term['type'] == TERM_MENTION) {
$contact = Contact::getDetailsByURL($term['url']);
if (!empty($contact['addr'])) {
$mention = '@' . $contact['addr'];
} else {
$mention = '@' . $term['url'];
}
$tags[] = ['type' => 'Mention', 'href' => $term['url'], 'name' => $mention];
}
}
return $tags;
}
/**
* Adds attachment data to the JSON document
*
* @param array $item Data of the item that is to be posted
* @param text $type Object type
*
* @return array with attachment data
*/
private static function createAttachmentList($item, $type)
{
$attachments = [];
$arr = explode('[/attach],', $item['attach']);
if (count($arr)) {
foreach ($arr as $r) {
$matches = false;
$cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|', $r, $matches);
if ($cnt) {
$attributes = ['type' => 'Document',
'mediaType' => $matches[3],
'url' => $matches[1],
'name' => null];
if (trim($matches[4]) != '') {
$attributes['name'] = trim($matches[4]);
}
$attachments[] = $attributes;
}
}
}
if ($type != 'Note') {
return $attachments;
}
// Simplify image codes
$body = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $item['body']);
// Grab all pictures and create attachments out of them
if (preg_match_all("/\[img\]([^\[\]]*)\[\/img\]/Usi", $body, $pictures)) {
foreach ($pictures[1] as $picture) {
$imgdata = Image::getInfoFromURL($picture);
if ($imgdata) {
$attachments[] = ['type' => 'Document',
'mediaType' => $imgdata['mime'],
'url' => $picture,
'name' => null];
}
}
}
return $attachments;
}
/**
* Remove image elements and replaces them with links to the image
*
* @param string $body
*
* @return string with replaced elements
*/
private static function removePictures($body)
{
// Simplify image codes
$body = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $body);
$body = preg_replace("/\[url=([^\[\]]*)\]\[img\](.*)\[\/img\]\[\/url\]/Usi", '[url]$1[/url]', $body);
$body = preg_replace("/\[img\]([^\[\]]*)\[\/img\]/Usi", '[url]$1[/url]', $body);
return $body;
}
/**
* Fetches the "context" value for a givem item array from the "conversation" table
*
* @param array $item
*
* @return string with context url
*/
private static function fetchContextURLForItem($item)
{
$conversation = DBA::selectFirst('conversation', ['conversation-href', 'conversation-uri'], ['item-uri' => $item['parent-uri']]);
if (DBA::isResult($conversation) && !empty($conversation['conversation-href'])) {
$context_uri = $conversation['conversation-href'];
} elseif (DBA::isResult($conversation) && !empty($conversation['conversation-uri'])) {
$context_uri = $conversation['conversation-uri'];
} else {
$context_uri = $item['parent-uri'] . '#context';
}
return $context_uri;
}
/**
* Returns if the post contains sensitive content ("nsfw")
*
* @param integer $item_id
*
* @return boolean
*/
private static function isSensitive($item_id)
{
$condition = ['otype' => TERM_OBJ_POST, 'oid' => $item_id, 'type' => TERM_HASHTAG, 'term' => 'nsfw'];
return DBA::exists('term', $condition);
}
/**
* Creates a note/article object array
*
* @param array $item
*
* @return array with the object data
*/
public static function createNote($item)
{
if (!empty($item['title'])) {
$type = 'Article';
} else {
$type = 'Note';
}
if ($item['deleted']) {
$type = 'Tombstone';
}
$data = [];
$data['id'] = $item['uri'];
$data['type'] = $type;
if ($item['deleted']) {
return $data;
}
$data['summary'] = null; // Ignore by now
if ($item['uri'] != $item['thr-parent']) {
$data['inReplyTo'] = $item['thr-parent'];
} else {
$data['inReplyTo'] = null;
}
$data['diaspora:guid'] = $item['guid'];
$data['published'] = DateTimeFormat::utc($item['created'] . '+00:00', DateTimeFormat::ATOM);
if ($item['created'] != $item['edited']) {
$data['updated'] = DateTimeFormat::utc($item['edited'] . '+00:00', DateTimeFormat::ATOM);
}
$data['url'] = $item['plink'];
$data['attributedTo'] = $item['author-link'];
$data['sensitive'] = self::isSensitive($item['id']);
$data['context'] = self::fetchContextURLForItem($item);
if (!empty($item['title'])) {
$data['name'] = BBCode::toPlaintext($item['title'], false);
}
$body = $item['body'];
if ($type == 'Note') {
$body = self::removePictures($body);
}
$data['content'] = BBCode::convert($body, false, 7);
$data['source'] = ['content' => $item['body'], 'mediaType' => "text/bbcode"];
if (!empty($item['signed_text']) && ($item['uri'] != $item['thr-parent'])) {
$data['diaspora:comment'] = $item['signed_text'];
}
$data['attachment'] = self::createAttachmentList($item, $type);
$data['tag'] = self::createTagList($item);
if (!empty($item['coord']) || !empty($item['location'])) {
$data['location'] = self::createLocation($item);
}
if (!empty($item['app'])) {
$data['generator'] = ['type' => 'Application', 'name' => $item['app']];
}
$data = array_merge($data, self::createPermissionBlockForItem($item));
return $data;
}
/**
* Creates an announce object entry
*
* @param array $item
*
* @return string with announced object url
*/
public static function createAnnounce($item)
{
$announce = api_share_as_retweet($item);
if (empty($announce['plink'])) {
return self::createNote($item);
}
return $announce['plink'];
}
/**
* Transmits a contact suggestion to a given inbox
*
* @param integer $uid User ID
* @param string $inbox Target inbox
* @param integer $suggestion_id Suggestion ID
*/
public static function sendContactSuggestion($uid, $inbox, $suggestion_id)
{
$owner = User::getOwnerDataById($uid);
$profile = APContact::getByURL($owner['url']);
$suggestion = DBA::selectFirst('fsuggest', ['url', 'note', 'created'], ['id' => $suggestion_id]);
$data = ['@context' => 'https://www.w3.org/ns/activitystreams',
'id' => System::baseUrl() . '/activity/' . System::createGUID(),
'type' => 'Announce',
'actor' => $owner['url'],
'object' => $suggestion['url'],
'content' => $suggestion['note'],
'instrument' => ['type' => 'Service', 'name' => BaseObject::getApp()->getUserAgent()],
'to' => [ActivityPub::PUBLIC_COLLECTION],
'cc' => []];
$signed = LDSignature::sign($data, $owner);
logger('Deliver profile deletion for user ' . $uid . ' to ' . $inbox . ' via ActivityPub', LOGGER_DEBUG);
HTTPSignature::transmit($signed, $inbox, $uid);
}
/**
* Transmits a profile deletion to a given inbox
*
* @param integer $uid User ID
* @param string $inbox Target inbox
*/
public static function sendProfileDeletion($uid, $inbox)
{
$owner = User::getOwnerDataById($uid);
$profile = APContact::getByURL($owner['url']);
$data = ['@context' => 'https://www.w3.org/ns/activitystreams',
'id' => System::baseUrl() . '/activity/' . System::createGUID(),
'type' => 'Delete',
'actor' => $owner['url'],
'object' => $owner['url'],
'published' => DateTimeFormat::utcNow(DateTimeFormat::ATOM),
'instrument' => ['type' => 'Service', 'name' => BaseObject::getApp()->getUserAgent()],
'to' => [ActivityPub::PUBLIC_COLLECTION],
'cc' => []];
$signed = LDSignature::sign($data, $owner);
logger('Deliver profile deletion for user ' . $uid . ' to ' . $inbox . ' via ActivityPub', LOGGER_DEBUG);
HTTPSignature::transmit($signed, $inbox, $uid);
}
/**
* Transmits a profile change to a given inbox
*
* @param integer $uid User ID
* @param string $inbox Target inbox
*/
public static function sendProfileUpdate($uid, $inbox)
{
$owner = User::getOwnerDataById($uid);
$profile = APContact::getByURL($owner['url']);
$data = ['@context' => 'https://www.w3.org/ns/activitystreams',
'id' => System::baseUrl() . '/activity/' . System::createGUID(),
'type' => 'Update',
'actor' => $owner['url'],
'object' => self::getProfile($uid),
'published' => DateTimeFormat::utcNow(DateTimeFormat::ATOM),
'instrument' => ['type' => 'Service', 'name' => BaseObject::getApp()->getUserAgent()],
'to' => [$profile['followers']],
'cc' => []];
$signed = LDSignature::sign($data, $owner);
logger('Deliver profile update for user ' . $uid . ' to ' . $inbox . ' via ActivityPub', LOGGER_DEBUG);
HTTPSignature::transmit($signed, $inbox, $uid);
}
/**
* Transmits a given activity to a target
*
* @param array $activity
* @param string $target Target profile
* @param integer $uid User ID
*/
public static function sendActivity($activity, $target, $uid)
{
$profile = APContact::getByURL($target);
$owner = User::getOwnerDataById($uid);
$data = ['@context' => 'https://www.w3.org/ns/activitystreams',
'id' => System::baseUrl() . '/activity/' . System::createGUID(),
'type' => $activity,
'actor' => $owner['url'],
'object' => $profile['url'],
'instrument' => ['type' => 'Service', 'name' => BaseObject::getApp()->getUserAgent()],
'to' => $profile['url']];
logger('Sending activity ' . $activity . ' to ' . $target . ' for user ' . $uid, LOGGER_DEBUG);
$signed = LDSignature::sign($data, $owner);
HTTPSignature::transmit($signed, $profile['inbox'], $uid);
}
/**
* Transmit a message that the contact request had been accepted
*
* @param string $target Target profile
* @param $id
* @param integer $uid User ID
*/
public static function sendContactAccept($target, $id, $uid)
{
$profile = APContact::getByURL($target);
$owner = User::getOwnerDataById($uid);
$data = ['@context' => 'https://www.w3.org/ns/activitystreams',
'id' => System::baseUrl() . '/activity/' . System::createGUID(),
'type' => 'Accept',
'actor' => $owner['url'],
'object' => ['id' => $id, 'type' => 'Follow',
'actor' => $profile['url'],
'object' => $owner['url']],
'instrument' => ['type' => 'Service', 'name' => BaseObject::getApp()->getUserAgent()],
'to' => $profile['url']];
logger('Sending accept to ' . $target . ' for user ' . $uid . ' with id ' . $id, LOGGER_DEBUG);
$signed = LDSignature::sign($data, $owner);
HTTPSignature::transmit($signed, $profile['inbox'], $uid);
}
/**
* Reject a contact request or terminates the contact relation
*
* @param string $target Target profile
* @param $id
* @param integer $uid User ID
*/
public static function sendContactReject($target, $id, $uid)
{
$profile = APContact::getByURL($target);
$owner = User::getOwnerDataById($uid);
$data = ['@context' => 'https://www.w3.org/ns/activitystreams',
'id' => System::baseUrl() . '/activity/' . System::createGUID(),
'type' => 'Reject',
'actor' => $owner['url'],
'object' => ['id' => $id, 'type' => 'Follow',
'actor' => $profile['url'],
'object' => $owner['url']],
'instrument' => ['type' => 'Service', 'name' => BaseObject::getApp()->getUserAgent()],
'to' => $profile['url']];
logger('Sending reject to ' . $target . ' for user ' . $uid . ' with id ' . $id, LOGGER_DEBUG);
$signed = LDSignature::sign($data, $owner);
HTTPSignature::transmit($signed, $profile['inbox'], $uid);
}
/**
* Transmits a message that we don't want to follow this contact anymore
*
* @param string $target Target profile
* @param integer $uid User ID
*/
public static function sendContactUndo($target, $uid)
{
$profile = APContact::getByURL($target);
$id = System::baseUrl() . '/activity/' . System::createGUID();
$owner = User::getOwnerDataById($uid);
$data = ['@context' => 'https://www.w3.org/ns/activitystreams',
'id' => $id,
'type' => 'Undo',
'actor' => $owner['url'],
'object' => ['id' => $id, 'type' => 'Follow',
'actor' => $owner['url'],
'object' => $profile['url']],
'instrument' => ['type' => 'Service', 'name' => BaseObject::getApp()->getUserAgent()],
'to' => $profile['url']];
logger('Sending undo to ' . $target . ' for user ' . $uid . ' with id ' . $id, LOGGER_DEBUG);
$signed = LDSignature::sign($data, $owner);
HTTPSignature::transmit($signed, $profile['inbox'], $uid);
}
}

View file

@ -81,7 +81,8 @@ class DFRN
return [];
}
$user['importer_uid'] = $user['uid'];
$user['importer_uid'] = $user['uid'];
$user['uprvkey'] = $user['prvkey'];
} else {
$user = ['importer_uid' => 0, 'uprvkey' => '', 'timezone' => 'UTC',
'nickname' => '', 'sprvkey' => '', 'spubkey' => '',
@ -1163,15 +1164,17 @@ class DFRN
* @return int Deliver status. Negative values mean an error.
* @todo Add array type-hint for $owner, $contact
*/
public static function deliver($owner, $contact, $atom, $dissolve = false)
public static function deliver($owner, $contact, $atom, $dissolve = false, $legacy_transport = false)
{
$a = get_app();
// At first try the Diaspora transport layer
$ret = self::transmit($owner, $contact, $atom);
if ($ret >= 200) {
logger('Delivery via Diaspora transport layer was successful with status ' . $ret);
return $ret;
if (!$dissolve && !$legacy_transport) {
$curlResult = self::transmit($owner, $contact, $atom);
if ($curlResult >= 200) {
logger('Delivery via Diaspora transport layer was successful with status ' . $curlResult);
return $curlResult;
}
}
$idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
@ -1208,16 +1211,16 @@ class DFRN
logger('dfrn_deliver: ' . $url);
$ret = Network::curl($url);
$curlResult = Network::curl($url);
if (!empty($ret["errno"]) && ($ret['errno'] == CURLE_OPERATION_TIMEDOUT)) {
if ($curlResult->isTimeout()) {
Contact::markForArchival($contact);
return -2; // timed out
}
$xml = $ret['body'];
$xml = $curlResult->getBody();
$curl_stat = $a->get_curl_code();
$curl_stat = $curlResult->getReturnCode();
if (empty($curl_stat)) {
Contact::markForArchival($contact);
return -3; // timed out
@ -1365,17 +1368,19 @@ class DFRN
logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars, true), LOGGER_DATA);
$xml = Network::post($contact['notify'], $postvars);
$postResult = Network::post($contact['notify'], $postvars);
$xml = $postResult->getBody();
logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
$curl_stat = $a->get_curl_code();
$curl_stat = $postResult->getReturnCode();
if (empty($curl_stat) || empty($xml)) {
Contact::markForArchival($contact);
return -9; // timed out
}
if (($curl_stat == 503) && stristr($a->get_curl_headers(), 'retry-after')) {
if (($curl_stat == 503) && stristr($postResult->getHeader(), 'retry-after')) {
Contact::markForArchival($contact);
return -10;
}
@ -1464,16 +1469,17 @@ class DFRN
$content_type = ($public_batch ? "application/magic-envelope+xml" : "application/json");
$xml = Network::post($dest_url, $envelope, ["Content-Type: ".$content_type]);
$postResult = Network::post($dest_url, $envelope, ["Content-Type: ".$content_type]);
$xml = $postResult->getBody();
$curl_stat = $a->get_curl_code();
$curl_stat = $postResult->getReturnCode();
if (empty($curl_stat) || empty($xml)) {
logger('Empty answer from ' . $contact['id'] . ' - ' . $dest_url);
Contact::markForArchival($contact);
return -9; // timed out
}
if (($curl_stat == 503) && (stristr($a->get_curl_headers(), 'retry-after'))) {
if (($curl_stat == 503) && (stristr($postResult->getHeader(), 'retry-after'))) {
Contact::markForArchival($contact);
return -10;
}
@ -2252,6 +2258,11 @@ class DFRN
if ($Blink && link_compare($Blink, System::baseUrl() . "/profile/" . $importer["nickname"])) {
$author = DBA::selectFirst('contact', ['name', 'thumb', 'url'], ['id' => $item['author-id']]);
$item['id'] = $posted_id;
$parent = Item::selectFirst(['id'], ['uri' => $item['parent-uri'], 'uid' => $importer["importer_uid"]]);
$item["parent"] = $parent['id'];
// send a notification
notification(
[
@ -2341,6 +2352,12 @@ class DFRN
if (Item::exists($condition)) {
return false;
}
// The owner of an activity must be the author
$item["owner-name"] = $item["author-name"];
$item["owner-link"] = $item["author-link"];
$item["owner-avatar"] = $item["author-avatar"];
$item["owner-id"] = $item["author-id"];
} else {
$is_like = false;
}
@ -2402,8 +2419,11 @@ class DFRN
break;
case "enclosure":
$enclosure = $href;
if (strlen($item["attach"])) {
if (!empty($item["attach"])) {
$item["attach"] .= ",";
} else {
$item["attach"] = "";
}
$item["attach"] .= '[attach]href="' . $href . '" length="' . $length . '" type="' . $type . '" title="' . $title . '"[/attach]';
@ -2479,7 +2499,7 @@ class DFRN
/// @todo Do we really need this check for HTML elements? (It was copied from the old function)
if ((strpos($item['body'], '<') !== false) && (strpos($item['body'], '>') !== false)) {
$base_url = get_app()->get_baseurl();
$base_url = get_app()->getBaseURL();
$item['body'] = reltoabs($item['body'], $base_url);
$item['body'] = html2bb_video($item['body']);

View file

@ -365,15 +365,16 @@ class Diaspora
/**
* @brief: Decodes incoming Diaspora message in the new format
*
* @param array $importer Array of the importer user
* @param string $raw raw post message
* @param array $importer Array of the importer user
* @param string $raw raw post message
* @param boolean $no_exit Don't do an http exit on error
*
* @return array
* 'message' -> decoded Diaspora XML message
* 'author' -> author diaspora handle
* 'key' -> author public key (converted to pkcs#8)
*/
public static function decodeRaw(array $importer, $raw)
public static function decodeRaw(array $importer, $raw, $no_exit = false)
{
$data = json_decode($raw);
@ -388,7 +389,11 @@ class Diaspora
if (!is_object($j_outer_key_bundle)) {
logger('Outer Salmon did not verify. Discarding.');
System::httpExit(400);
if ($no_exit) {
return false;
} else {
System::httpExit(400);
}
}
$outer_iv = base64_decode($j_outer_key_bundle->iv);
@ -403,7 +408,11 @@ class Diaspora
if (!is_object($basedom)) {
logger('Received data does not seem to be an XML. Discarding. '.$xml);
System::httpExit(400);
if ($no_exit) {
return false;
} else {
System::httpExit(400);
}
}
$base = $basedom->children(NAMESPACE_SALMON_ME);
@ -425,19 +434,31 @@ class Diaspora
$author_addr = base64_decode($key_id);
if ($author_addr == '') {
logger('No author could be decoded. Discarding. Message: ' . $xml);
System::httpExit(400);
if ($no_exit) {
return false;
} else {
System::httpExit(400);
}
}
$key = self::key($author_addr);
if ($key == '') {
logger("Couldn't get a key for handle " . $author_addr . ". Discarding.");
System::httpExit(400);
if ($no_exit) {
return false;
} else {
System::httpExit(400);
}
}
$verify = Crypto::rsaVerify($signed_data, $signature, $key);
if (!$verify) {
logger('Message did not verify. Discarding.');
System::httpExit(400);
if ($no_exit) {
return false;
} else {
System::httpExit(400);
}
}
return ['message' => (string)base64url_decode($base->data),
@ -928,6 +949,7 @@ class Diaspora
$person = DBA::selectFirst('fcontact', [], ['network' => Protocol::DIASPORA, 'addr' => $handle]);
if (!DBA::isResult($person)) {
$person = $r;
$person['id'] = 0;
}
}
}
@ -1571,17 +1593,13 @@ class Diaspora
if (DBA::isResult($item)) {
return $item["uri"];
} elseif (!$onlyfound) {
$contact = Contact::getDetailsByAddr($author, 0);
if (!empty($contact['network'])) {
$prefix = 'urn:X-' . $contact['network'] . ':';
} else {
// This fallback should happen most unlikely
$prefix = 'urn:X-dspr:';
}
$person = self::personByHandle($author);
$author_parts = explode('@', $author);
$parts = parse_url($person['url']);
unset($parts['path']);
$host_url = Network::unparseURL($parts);
return $prefix . $author_parts[1] . ':' . $author_parts[0] . ':'. $guid;
return $host_url . '/objects/' . $guid;
}
return "";
@ -1952,11 +1970,8 @@ class Diaspora
$datarray["contact-id"] = $author_contact["cid"];
$datarray["network"] = $author_contact["network"];
$datarray["author-link"] = $person["url"];
$datarray["author-id"] = Contact::getIdForURL($person["url"], 0);
$datarray["owner-link"] = $contact["url"];
$datarray["owner-id"] = Contact::getIdForURL($contact["url"], 0);
$datarray["owner-link"] = $datarray["author-link"] = $person["url"];
$datarray["owner-id"] = $datarray["author-id"] = Contact::getIdForURL($person["url"], 0);
$datarray["guid"] = $guid;
$datarray["uri"] = self::getUriFromGuid($author, $guid);
@ -3065,8 +3080,8 @@ class Diaspora
if (!intval(Config::get("system", "diaspora_test"))) {
$content_type = (($public_batch) ? "application/magic-envelope+xml" : "application/json");
Network::post($dest_url."/", $envelope, ["Content-Type: ".$content_type]);
$return_code = $a->get_curl_code();
$postResult = Network::post($dest_url."/", $envelope, ["Content-Type: ".$content_type]);
$return_code = $postResult->getReturnCode();
} else {
logger("test_mode");
return 200;
@ -3075,7 +3090,7 @@ class Diaspora
logger("transmit: ".$logid."-".$guid." to ".$dest_url." returns: ".$return_code);
if (!$return_code || (($return_code == 503) && (stristr($a->get_curl_headers(), "retry-after")))) {
if (!$return_code || (($return_code == 503) && (stristr($postResult->getHeader(), "retry-after")))) {
if (!$no_queue && !empty($contact['contact-type']) && ($contact['contact-type'] != Contact::ACCOUNT_TYPE_RELAY)) {
logger("queue message");
// queue message for redelivery
@ -3183,7 +3198,7 @@ class Diaspora
$author = self::myHandle($owner);
$message = ["author" => $author,
"guid" => System::createGUID(32),
"guid" => System::createUUID(),
"parent_type" => "Post",
"parent_guid" => $item["guid"]];
@ -3415,12 +3430,9 @@ class Diaspora
/// @todo - establish "all day" events in Friendica
$eventdata["all_day"] = "false";
if (!$event['adjust']) {
$eventdata['timezone'] = 'UTC';
if (!$event['adjust'] && $user['timezone']) {
$eventdata['timezone'] = $user['timezone'];
if ($eventdata['timezone'] == "") {
$eventdata['timezone'] = 'UTC';
}
}
if ($event['start']) {
@ -3475,7 +3487,7 @@ class Diaspora
$myaddr = self::myHandle($owner);
$public = (($item["private"]) ? "false" : "true");
$public = ($item["private"] ? "false" : "true");
$created = DateTimeFormat::utc($item["created"], DateTimeFormat::ATOM);
@ -3735,13 +3747,13 @@ class Diaspora
*
* @return string The message
*/
private static function messageFromSignature(array $item, array $signature)
private static function messageFromSignature(array $item)
{
// Split the signed text
$signed_parts = explode(";", $signature['signed_text']);
$signed_parts = explode(";", $item['signed_text']);
if ($item["deleted"]) {
$message = ["author" => $signature['signer'],
$message = ["author" => $item['signer'],
"target_guid" => $signed_parts[0],
"target_type" => $signed_parts[1]];
} elseif (in_array($item["verb"], [ACTIVITY_LIKE, ACTIVITY_DISLIKE])) {
@ -3750,7 +3762,7 @@ class Diaspora
"parent_guid" => $signed_parts[3],
"parent_type" => $signed_parts[2],
"positive" => $signed_parts[0],
"author_signature" => $signature['signature'],
"author_signature" => $item['signature'],
"parent_author_signature" => ""];
} else {
// Remove the comment guid
@ -3769,7 +3781,7 @@ class Diaspora
"guid" => $guid,
"parent_guid" => $parent_guid,
"text" => implode(";", $signed_parts),
"author_signature" => $signature['signature'],
"author_signature" => $item['signature'],
"parent_author_signature" => ""];
}
return $message;
@ -3797,20 +3809,12 @@ class Diaspora
logger("Got relayable data ".$type." for item ".$item["guid"]." (".$item["id"].")", LOGGER_DEBUG);
// fetch the original signature
$fields = ['signed_text', 'signature', 'signer'];
$signature = DBA::selectFirst('sign', $fields, ['iid' => $item["id"]]);
if (!DBA::isResult($signature)) {
logger("Couldn't fetch signatur for item ".$item["guid"]." (".$item["id"].")", LOGGER_DEBUG);
return false;
}
// Old way - is used by the internal Friendica functions
/// @todo Change all signatur storing functions to the new format
if ($signature['signed_text'] && $signature['signature'] && $signature['signer']) {
$message = self::messageFromSignature($item, $signature);
if ($item['signed_text'] && $item['signature'] && $item['signer']) {
$message = self::messageFromSignature($item);
} else {// New way
$msg = json_decode($signature['signed_text'], true);
$msg = json_decode($item['signed_text'], true);
$message = [];
if (is_array($msg)) {
@ -3827,7 +3831,7 @@ class Diaspora
$message[$field] = $data;
}
} else {
logger("Signature text for item ".$item["guid"]." (".$item["id"].") couldn't be extracted: ".$signature['signed_text'], LOGGER_DEBUG);
logger("Signature text for item ".$item["guid"]." (".$item["id"].") couldn't be extracted: ".$item['signed_text'], LOGGER_DEBUG);
}
}

View file

@ -271,9 +271,14 @@ class Feed {
}
$updated = XML::getFirstNodeValue($xpath, 'atom:updated/text()', $entry);
if (empty($updated)) {
if (empty($updated) && !empty($published)) {
$updated = $published;
}
if (empty($published) && !empty($updated)) {
$published = $updated;
}
if ($published != "") {
$item["created"] = $published;
}
@ -425,7 +430,7 @@ class Feed {
// Distributed items should have a well formatted URI.
// Additionally we have to avoid conflicts with identical URI between imported feeds and these items.
if ($notify) {
$item['guid'] = Item::guidFromUri($orig_plink, $a->get_hostname());
$item['guid'] = Item::guidFromUri($orig_plink, $a->getHostName());
unset($item['uri']);
unset($item['parent-uri']);

View file

@ -74,6 +74,16 @@ class OStatus
$author["contact-id"] = $contact["id"];
$contact = null;
/*
This here would be better, but we would get problems with contacts from the statusnet addon
This is kept here as a reminder for the future
$cid = Contact::getIdForURL($author["author-link"], $importer["uid"]);
if ($cid) {
$contact = DBA::selectFirst('contact', [], ['id' => $cid]);
}
*/
if ($aliaslink != '') {
$condition = ["`uid` = ? AND `alias` = ? AND `network` != ? AND `rel` IN (?, ?)",
$importer["uid"], $aliaslink, Protocol::STATUSNET,
@ -219,7 +229,7 @@ class OStatus
$gcid = GContact::update($contact);
GContact::link($gcid, $contact["uid"], $contact["id"]);
} else {
} elseif ($contact["network"] != Protocol::DFRN) {
$contact = null;
}
@ -312,7 +322,7 @@ class OStatus
self::$conv_list = [];
}
logger("Import OStatus message", LOGGER_DEBUG);
logger('Import OStatus message for user ' . $importer['uid'], LOGGER_DEBUG);
if ($xml == "") {
return false;
@ -351,7 +361,7 @@ class OStatus
$header["origin"] = 0;
$header["gravity"] = GRAVITY_COMMENT;
if (!is_object($doc->firstChild)) {
if (!is_object($doc->firstChild) || empty($doc->firstChild->tagName)) {
return false;
}
@ -726,21 +736,21 @@ class OStatus
self::$conv_list[$conversation] = true;
$conversation_data = Network::curl($conversation, false, $redirects, ['accept_content' => 'application/atom+xml, text/html']);
$curlResult = Network::curl($conversation, false, $redirects, ['accept_content' => 'application/atom+xml, text/html']);
if (!$conversation_data['success']) {
if (!$curlResult->isSuccess()) {
return;
}
$xml = '';
if (stristr($conversation_data['header'], 'Content-Type: application/atom+xml')) {
$xml = $conversation_data['body'];
if (stristr($curlResult->getHeader(), 'Content-Type: application/atom+xml')) {
$xml = $curlResult->getBody();
}
if ($xml == '') {
$doc = new DOMDocument();
if (!@$doc->loadHTML($conversation_data['body'])) {
if (!@$doc->loadHTML($curlResult->getBody())) {
return;
}
$xpath = new DOMXPath($doc);
@ -757,8 +767,8 @@ class OStatus
if ($file != '') {
$conversation_atom = Network::curl($attribute['href']);
if ($conversation_atom['success']) {
$xml = $conversation_atom['body'];
if ($conversation_atom->isSuccess()) {
$xml = $conversation_atom->getBody();
}
}
}
@ -870,15 +880,15 @@ class OStatus
return;
}
$self_data = Network::curl($self);
$curlResult = Network::curl($self);
if (!$self_data['success']) {
if (!$curlResult->isSuccess()) {
return;
}
// We reformat the XML to make it better readable
$doc = new DOMDocument();
$doc->loadXML($self_data['body']);
$doc->loadXML($curlResult->getBody());
$doc->preserveWhiteSpace = false;
$doc->formatOutput = true;
$xml = $doc->saveXML();
@ -915,22 +925,22 @@ class OStatus
}
$stored = false;
$related_data = Network::curl($related, false, $redirects, ['accept_content' => 'application/atom+xml, text/html']);
$curlResult = Network::curl($related, false, $redirects, ['accept_content' => 'application/atom+xml, text/html']);
if (!$related_data['success']) {
if (!$curlResult->isSuccess()) {
return;
}
$xml = '';
if (stristr($related_data['header'], 'Content-Type: application/atom+xml')) {
logger('Directly fetched XML for URI '.$related_uri, LOGGER_DEBUG);
$xml = $related_data['body'];
if (stristr($curlResult->getHeader(), 'Content-Type: application/atom+xml')) {
logger('Directly fetched XML for URI ' . $related_uri, LOGGER_DEBUG);
$xml = $curlResult->getBody();
}
if ($xml == '') {
$doc = new DOMDocument();
if (!@$doc->loadHTML($related_data['body'])) {
if (!@$doc->loadHTML($curlResult->getBody())) {
return;
}
$xpath = new DOMXPath($doc);
@ -946,11 +956,11 @@ class OStatus
}
}
if ($atom_file != '') {
$related_atom = Network::curl($atom_file);
$curlResult = Network::curl($atom_file);
if ($related_atom['success']) {
logger('Fetched XML for URI '.$related_uri, LOGGER_DEBUG);
$xml = $related_atom['body'];
if ($curlResult->isSuccess()) {
logger('Fetched XML for URI ' . $related_uri, LOGGER_DEBUG);
$xml = $curlResult->getBody();
}
}
}
@ -958,22 +968,22 @@ class OStatus
// Workaround for older GNU Social servers
if (($xml == '') && strstr($related, '/notice/')) {
$related_atom = Network::curl(str_replace('/notice/', '/api/statuses/show/', $related).'.atom');
$curlResult = Network::curl(str_replace('/notice/', '/api/statuses/show/', $related).'.atom');
if ($related_atom['success']) {
logger('GNU Social workaround to fetch XML for URI '.$related_uri, LOGGER_DEBUG);
$xml = $related_atom['body'];
if ($curlResult->isSuccess()) {
logger('GNU Social workaround to fetch XML for URI ' . $related_uri, LOGGER_DEBUG);
$xml = $curlResult->getBody();
}
}
// Even more worse workaround for GNU Social ;-)
if ($xml == '') {
$related_guess = OStatus::convertHref($related_uri);
$related_atom = Network::curl(str_replace('/notice/', '/api/statuses/show/', $related_guess).'.atom');
$curlResult = Network::curl(str_replace('/notice/', '/api/statuses/show/', $related_guess).'.atom');
if ($related_atom['success']) {
logger('GNU Social workaround 2 to fetch XML for URI '.$related_uri, LOGGER_DEBUG);
$xml = $related_atom['body'];
if ($curlResult->isSuccess()) {
logger('GNU Social workaround 2 to fetch XML for URI ' . $related_uri, LOGGER_DEBUG);
$xml = $curlResult->getBody();
}
}
@ -1820,7 +1830,7 @@ class OStatus
}
$item["uri"] = $item['parent-uri'] = $item['thr-parent']
= 'tag:'.get_app()->get_hostname().
= 'tag:'.get_app()->getHostName().
','.date('Y-m-d').':'.$action.':'.$owner['uid'].
':person:'.$connect_id.':'.$item['created'];
@ -1994,8 +2004,7 @@ class OStatus
}
if (intval($item["parent"]) > 0) {
$conversation_href = System::baseUrl()."/display/".$owner["nick"]."/".$item["parent"];
$conversation_uri = $conversation_href;
$conversation_href = $conversation_uri = str_replace('/objects/', '/context/', $item['parent-uri']);
if (isset($parent_item)) {
$conversation = DBA::selectFirst('conversation', ['conversation-uri', 'conversation-href'], ['item-uri' => $parent_item]);

View file

@ -86,13 +86,14 @@ class PortableContact
logger('load: ' . $url, LOGGER_DEBUG);
$s = Network::fetchUrl($url);
$fetchresult = Network::fetchUrlFull($url);
$s = $fetchresult->getBody();
logger('load: returns ' . $s, LOGGER_DATA);
logger('load: return code: ' . $a->get_curl_code(), LOGGER_DEBUG);
logger('load: return code: ' . $fetchresult->getReturnCode(), LOGGER_DEBUG);
if (($a->get_curl_code() > 299) || (! $s)) {
if (($fetchresult->getReturnCode() > 299) || (! $s)) {
return;
}
@ -290,8 +291,8 @@ class PortableContact
}
// Fetch the host-meta to check if this really is a server
$serverret = Network::curl($server_url."/.well-known/host-meta");
if (!$serverret["success"]) {
$curlResult = Network::curl($server_url."/.well-known/host-meta");
if (!$curlResult->isSuccess()) {
return "";
}
@ -333,7 +334,7 @@ class PortableContact
$server_url = normalise_link(self::detectServer($profile));
}
if (!in_array($gcontacts[0]["network"], [Protocol::DFRN, Protocol::DIASPORA, Protocol::FEED, Protocol::OSTATUS, ""])) {
if (!in_array($gcontacts[0]["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::FEED, Protocol::OSTATUS, ""])) {
logger("Profile ".$profile.": Network type ".$gcontacts[0]["network"]." can't be checked", LOGGER_DEBUG);
return false;
}
@ -370,10 +371,10 @@ class PortableContact
$server = q("SELECT `noscrape`, `network` FROM `gserver` WHERE `nurl` = '%s' AND `noscrape` != ''", DBA::escape(normalise_link($server_url)));
if ($server) {
$noscraperet = Network::curl($server[0]["noscrape"]."/".$gcontacts[0]["nick"]);
$curlResult = Network::curl($server[0]["noscrape"]."/".$gcontacts[0]["nick"]);
if ($noscraperet["success"] && ($noscraperet["body"] != "")) {
$noscrape = json_decode($noscraperet["body"], true);
if ($curlResult->isSuccess() && ($curlResult->getBody() != "")) {
$noscrape = json_decode($curlResult->getBody(), true);
if (is_array($noscrape)) {
$contact["network"] = $server[0]["network"];
@ -484,9 +485,9 @@ class PortableContact
GContact::update($contact);
$feedret = Network::curl($data["poll"]);
$curlResult = Network::curl($data["poll"]);
if (!$feedret["success"]) {
if (!$curlResult->isSuccess()) {
$fields = ['last_failure' => DateTimeFormat::utcNow()];
DBA::update('gcontact', $fields, ['nurl' => normalise_link($profile)]);
@ -496,7 +497,7 @@ class PortableContact
$doc = new DOMDocument();
/// @TODO Avoid error supression here
@$doc->loadXML($feedret["body"]);
@$doc->loadXML($curlResult->getBody());
$xpath = new DOMXPath($doc);
$xpath->registerNamespace('atom', "http://www.w3.org/2005/Atom");
@ -645,12 +646,12 @@ class PortableContact
*/
private static function fetchNodeinfo($server_url)
{
$serverret = Network::curl($server_url."/.well-known/nodeinfo");
if (!$serverret["success"]) {
$curlResult = Network::curl($server_url."/.well-known/nodeinfo");
if (!$curlResult->isSuccess()) {
return false;
}
$nodeinfo = json_decode($serverret['body'], true);
$nodeinfo = json_decode($curlResult->getBody(), true);
if (!is_array($nodeinfo) || !isset($nodeinfo['links'])) {
return false;
@ -698,13 +699,13 @@ class PortableContact
*/
private static function parseNodeinfo1($nodeinfo_url)
{
$serverret = Network::curl($nodeinfo_url);
$curlResult = Network::curl($nodeinfo_url);
if (!$serverret["success"]) {
if (!$curlResult->isSuccess()) {
return false;
}
$nodeinfo = json_decode($serverret['body'], true);
$nodeinfo = json_decode($curlResult->getBody(), true);
if (!is_array($nodeinfo)) {
return false;
@ -782,12 +783,12 @@ class PortableContact
*/
private static function parseNodeinfo2($nodeinfo_url)
{
$serverret = Network::curl($nodeinfo_url);
if (!$serverret["success"]) {
$curlResult = Network::curl($nodeinfo_url);
if (!$curlResult->isSuccess()) {
return false;
}
$nodeinfo = json_decode($serverret['body'], true);
$nodeinfo = json_decode($curlResult->getBody(), true);
if (!is_array($nodeinfo)) {
return false;
@ -997,38 +998,38 @@ class PortableContact
$server_url = str_replace("http://", "https://", $server_url);
// We set the timeout to 20 seconds since this operation should be done in no time if the server was vital
$serverret = Network::curl($server_url."/.well-known/host-meta", false, $redirects, ['timeout' => 20]);
$curlResult = Network::curl($server_url."/.well-known/host-meta", false, $redirects, ['timeout' => 20]);
// Quit if there is a timeout.
// But we want to make sure to only quit if we are mostly sure that this server url fits.
if (DBA::isResult($gserver) && ($orig_server_url == $server_url) &&
(!empty($serverret["errno"]) && ($serverret['errno'] == CURLE_OPERATION_TIMEDOUT))) {
($curlResult->isTimeout())) {
logger("Connection to server ".$server_url." timed out.", LOGGER_DEBUG);
DBA::update('gserver', ['last_failure' => DateTimeFormat::utcNow()], ['nurl' => normalise_link($server_url)]);
return false;
}
// Maybe the page is unencrypted only?
$xmlobj = @simplexml_load_string($serverret["body"], 'SimpleXMLElement', 0, "http://docs.oasis-open.org/ns/xri/xrd-1.0");
if (!$serverret["success"] || ($serverret["body"] == "") || empty($xmlobj) || !is_object($xmlobj)) {
$xmlobj = @simplexml_load_string($curlResult->getBody(), 'SimpleXMLElement', 0, "http://docs.oasis-open.org/ns/xri/xrd-1.0");
if (!$curlResult->isSuccess() || ($curlResult->getBody() == "") || empty($xmlobj) || !is_object($xmlobj)) {
$server_url = str_replace("https://", "http://", $server_url);
// We set the timeout to 20 seconds since this operation should be done in no time if the server was vital
$serverret = Network::curl($server_url."/.well-known/host-meta", false, $redirects, ['timeout' => 20]);
$curlResult = Network::curl($server_url."/.well-known/host-meta", false, $redirects, ['timeout' => 20]);
// Quit if there is a timeout
if (!empty($serverret["errno"]) && ($serverret['errno'] == CURLE_OPERATION_TIMEDOUT)) {
logger("Connection to server ".$server_url." timed out.", LOGGER_DEBUG);
if ($curlResult->isTimeout()) {
logger("Connection to server " . $server_url . " timed out.", LOGGER_DEBUG);
DBA::update('gserver', ['last_failure' => DateTimeFormat::utcNow()], ['nurl' => normalise_link($server_url)]);
return false;
}
$xmlobj = @simplexml_load_string($serverret["body"], 'SimpleXMLElement', 0, "http://docs.oasis-open.org/ns/xri/xrd-1.0");
$xmlobj = @simplexml_load_string($curlResult->getBody(), 'SimpleXMLElement', 0, "http://docs.oasis-open.org/ns/xri/xrd-1.0");
}
if (!$serverret["success"] || ($serverret["body"] == "") || empty($xmlobj) || !is_object($xmlobj)) {
if (!$curlResult->isSuccess() || ($curlResult->getBody() == "") || empty($xmlobj) || !is_object($xmlobj)) {
// Workaround for bad configured servers (known nginx problem)
if (!empty($serverret["debug"]) && !in_array($serverret["debug"]["http_code"], ["403", "404"])) {
if (!empty($curlResult->getInfo()) && !in_array($curlResult->getInfo()["http_code"], ["403", "404"])) {
$failure = true;
}
@ -1051,10 +1052,10 @@ class PortableContact
// Look for poco
if (!$failure) {
$serverret = Network::curl($server_url."/poco");
$curlResult = Network::curl($server_url."/poco");
if ($serverret["success"]) {
$data = json_decode($serverret["body"], true);
if ($curlResult->isSuccess()) {
$data = json_decode($curlResult->getBody(), true);
if (isset($data['totalResults'])) {
$registered_users = $data['totalResults'];
@ -1083,12 +1084,12 @@ class PortableContact
if (!$failure) {
// Test for Diaspora, Hubzilla, Mastodon or older Friendica servers
$serverret = Network::curl($server_url);
$curlResult = Network::curl($server_url);
if (!$serverret["success"] || ($serverret["body"] == "")) {
if (!$curlResult->isSuccess() || ($curlResult->getBody() == "")) {
$failure = true;
} else {
$server = self::detectServerType($serverret["body"]);
$server = self::detectServerType($curlResult->getBody());
if (!empty($server)) {
$platform = $server['platform'];
@ -1097,7 +1098,7 @@ class PortableContact
$site_name = $server['site_name'];
}
$lines = explode("\n", $serverret["header"]);
$lines = explode("\n", $curlResult->getHeader());
if (count($lines)) {
foreach ($lines as $line) {
@ -1125,41 +1126,41 @@ class PortableContact
// Test for Statusnet
// Will also return data for Friendica and GNU Social - but it will be overwritten later
// The "not implemented" is a special treatment for really, really old Friendica versions
$serverret = Network::curl($server_url."/api/statusnet/version.json");
$curlResult = Network::curl($server_url."/api/statusnet/version.json");
if ($serverret["success"] && ($serverret["body"] != '{"error":"not implemented"}') &&
($serverret["body"] != '') && (strlen($serverret["body"]) < 30)) {
if ($curlResult->isSuccess() && ($curlResult->getBody() != '{"error":"not implemented"}') &&
($curlResult->getBody() != '') && (strlen($curlResult->getBody()) < 30)) {
$platform = "StatusNet";
// Remove junk that some GNU Social servers return
$version = str_replace(chr(239).chr(187).chr(191), "", $serverret["body"]);
$version = str_replace(chr(239).chr(187).chr(191), "", $curlResult->getBody());
$version = trim($version, '"');
$network = Protocol::OSTATUS;
}
// Test for GNU Social
$serverret = Network::curl($server_url."/api/gnusocial/version.json");
$curlResult = Network::curl($server_url."/api/gnusocial/version.json");
if ($serverret["success"] && ($serverret["body"] != '{"error":"not implemented"}') &&
($serverret["body"] != '') && (strlen($serverret["body"]) < 30)) {
if ($curlResult->isSuccess() && ($curlResult->getBody() != '{"error":"not implemented"}') &&
($curlResult->getBody() != '') && (strlen($curlResult->getBody()) < 30)) {
$platform = "GNU Social";
// Remove junk that some GNU Social servers return
$version = str_replace(chr(239) . chr(187) . chr(191), "", $serverret["body"]);
$version = str_replace(chr(239) . chr(187) . chr(191), "", $curlResult->getBody());
$version = trim($version, '"');
$network = Protocol::OSTATUS;
}
// Test for Mastodon
$orig_version = $version;
$serverret = Network::curl($server_url . "/api/v1/instance");
$curlResult = Network::curl($server_url . "/api/v1/instance");
if ($serverret["success"] && ($serverret["body"] != '')) {
$data = json_decode($serverret["body"], true);
if ($curlResult->isSuccess() && ($curlResult->getBody() != '')) {
$data = json_decode($curlResult->getBody(), true);
if (isset($data['version'])) {
$platform = "Mastodon";
$version = $data['version'];
$site_name = $data['title'];
$info = $data['description'];
$version = defaults($data, 'version', '');
$site_name = defaults($data, 'title', '');
$info = defaults($data, 'description', '');
$network = Protocol::OSTATUS;
}
@ -1176,10 +1177,10 @@ class PortableContact
if (!$failure) {
// Test for Hubzilla and Red
$serverret = Network::curl($server_url . "/siteinfo.json");
$curlResult = Network::curl($server_url . "/siteinfo.json");
if ($serverret["success"]) {
$data = json_decode($serverret["body"], true);
if ($curlResult->isSuccess()) {
$data = json_decode($curlResult->getBody(), true);
if (isset($data['url'])) {
$platform = $data['platform'];
@ -1213,10 +1214,10 @@ class PortableContact
}
} else {
// Test for Hubzilla, Redmatrix or Friendica
$serverret = Network::curl($server_url."/api/statusnet/config.json");
$curlResult = Network::curl($server_url."/api/statusnet/config.json");
if ($serverret["success"]) {
$data = json_decode($serverret["body"], true);
if ($curlResult->isSuccess()) {
$data = json_decode($curlResult->getBody(), true);
if (isset($data['site']['server'])) {
if (isset($data['site']['platform'])) {
@ -1286,10 +1287,10 @@ class PortableContact
// Query statistics.json. Optional package for Diaspora, Friendica and Redmatrix
if (!$failure) {
$serverret = Network::curl($server_url . "/statistics.json");
$curlResult = Network::curl($server_url . "/statistics.json");
if ($serverret["success"]) {
$data = json_decode($serverret["body"], true);
if ($curlResult->isSuccess()) {
$data = json_decode($curlResult->getBody(), true);
if (isset($data['version'])) {
$version = $data['version'];
@ -1350,14 +1351,14 @@ class PortableContact
// Check for noscrape
// Friendica servers could be detected as OStatus servers
if (!$failure && in_array($network, [Protocol::DFRN, Protocol::OSTATUS])) {
$serverret = Network::curl($server_url . "/friendica/json");
$curlResult = Network::curl($server_url . "/friendica/json");
if (!$serverret["success"]) {
$serverret = Network::curl($server_url . "/friendika/json");
if (!$curlResult->isSuccess()) {
$curlResult = Network::curl($server_url . "/friendika/json");
}
if ($serverret["success"]) {
$data = json_decode($serverret["body"], true);
if ($curlResult->isSuccess()) {
$data = json_decode($curlResult->getBody(), true);
if (isset($data['version'])) {
$network = Protocol::DFRN;
@ -1442,13 +1443,13 @@ class PortableContact
{
logger("Discover relay data for server " . $server_url, LOGGER_DEBUG);
$serverret = Network::curl($server_url . "/.well-known/x-social-relay");
$curlResult = Network::curl($server_url . "/.well-known/x-social-relay");
if (!$serverret["success"]) {
if (!$curlResult->isSuccess()) {
return;
}
$data = json_decode($serverret['body'], true);
$data = json_decode($curlResult->getBody(), true);
if (!is_array($data)) {
return;
@ -1538,13 +1539,13 @@ class PortableContact
*/
private static function fetchServerlist($poco)
{
$serverret = Network::curl($poco . "/@server");
$curlResult = Network::curl($poco . "/@server");
if (!$serverret["success"]) {
if (!$curlResult->isSuccess()) {
return;
}
$serverlist = json_decode($serverret['body'], true);
$serverlist = json_decode($curlResult->getBody(), true);
if (!is_array($serverlist)) {
return;
@ -1575,10 +1576,10 @@ class PortableContact
}
// Discover Friendica, Hubzilla and Diaspora servers
$serverdata = Network::fetchUrl("http://the-federation.info/pods.json");
$curlResult = Network::fetchUrl("http://the-federation.info/pods.json");
if (!empty($serverdata)) {
$servers = json_decode($serverdata, true);
if (!empty($curlResult)) {
$servers = json_decode($curlResult, true);
if (!empty($servers['pods'])) {
foreach ($servers['pods'] as $server) {
@ -1594,10 +1595,10 @@ class PortableContact
if (!empty($accesstoken)) {
$api = 'https://instances.social/api/1.0/instances/list?count=0';
$header = ['Authorization: Bearer '.$accesstoken];
$serverdata = Network::curl($api, false, $redirects, ['headers' => $header]);
$curlResult = Network::curl($api, false, $redirects, ['headers' => $header]);
if ($serverdata['success']) {
$servers = json_decode($serverdata['body'], true);
if ($curlResult->isSuccess()) {
$servers = json_decode($curlResult->getBody(), true);
foreach ($servers['instances'] as $server) {
$url = (is_null($server['https_score']) ? 'http' : 'https') . '://' . $server['name'];
@ -1613,9 +1614,9 @@ class PortableContact
//if (!Config::get('system','ostatus_disabled')) {
// $serverdata = "http://gstools.org/api/get_open_instances/";
// $result = Network::curl($serverdata);
// if ($result["success"]) {
// $servers = json_decode($result["body"], true);
// $curlResult = Network::curl($serverdata);
// if ($curlResult->isSuccess()) {
// $servers = json_decode($result->getBody(), true);
// foreach($servers['data'] as $server)
// self::checkServer($server['instance_address']);
@ -1643,10 +1644,10 @@ class PortableContact
logger("Fetch all users from the server " . $server["url"], LOGGER_DEBUG);
$retdata = Network::curl($url);
$curlResult = Network::curl($url);
if ($retdata["success"] && !empty($retdata["body"])) {
$data = json_decode($retdata["body"], true);
if ($curlResult->isSuccess() && !empty($curlResult->getBody())) {
$data = json_decode($curlResult->getBody(), true);
if (!empty($data)) {
self::discoverServer($data, 2);
@ -1666,11 +1667,11 @@ class PortableContact
$success = false;
$retdata = Network::curl($url);
$curlResult = Network::curl($url);
if ($retdata["success"] && !empty($retdata["body"])) {
if ($curlResult->isSuccess() && !empty($curlResult->getBody())) {
logger("Fetch all global contacts from the server " . $server["nurl"], LOGGER_DEBUG);
$data = json_decode($retdata["body"], true);
$data = json_decode($curlResult->getBody(), true);
if (!empty($data)) {
$success = self::discoverServer($data);
@ -1766,10 +1767,10 @@ class PortableContact
// Fetch all contacts from a given user from the other server
$url = $server['poco'] . '/' . $username . '/?fields=displayName,urls,photos,updated,network,aboutMe,currentLocation,tags,gender,contactType,generation';
$retdata = Network::curl($url);
$curlResult = Network::curl($url);
if (!empty($retdata['success'])) {
$data = json_decode($retdata["body"], true);
if ($curlResult->isSuccess()) {
$data = json_decode($curlResult["body"], true);
if (!empty($data)) {
self::discoverServer($data, 3);

View file

@ -133,13 +133,12 @@ class Salmon
$salmon = XML::fromArray($xmldata, $xml, false, $namespaces);
// slap them
Network::post($url, $salmon, [
$postResult = Network::post($url, $salmon, [
'Content-type: application/magic-envelope+xml',
'Content-length: ' . strlen($salmon)
]);
$a = get_app();
$return_code = $a->get_curl_code();
$return_code = $postResult->getReturnCode();
// check for success, e.g. 2xx
@ -159,11 +158,11 @@ class Salmon
$salmon = XML::fromArray($xmldata, $xml, false, $namespaces);
// slap them
Network::post($url, $salmon, [
$postResult = Network::post($url, $salmon, [
'Content-type: application/magic-envelope+xml',
'Content-length: ' . strlen($salmon)
]);
$return_code = $a->get_curl_code();
$return_code = $postResult->getReturnCode();
}
if ($return_code > 299) {
@ -182,10 +181,10 @@ class Salmon
$salmon = XML::fromArray($xmldata, $xml, false, $namespaces);
// slap them
Network::post($url, $salmon, [
$postResult = Network::post($url, $salmon, [
'Content-type: application/magic-envelope+xml',
'Content-length: ' . strlen($salmon)]);
$return_code = $a->get_curl_code();
$return_code = $postResult->getReturnCode();
}
logger('slapper for '.$url.' returned ' . $return_code);
@ -194,7 +193,7 @@ class Salmon
return -1;
}
if (($return_code == 503) && (stristr($a->get_curl_headers(), 'retry-after'))) {
if (($return_code == 503) && (stristr($postResult->getHeader(), 'retry-after'))) {
return -1;
}

View file

@ -9,7 +9,7 @@ use Smarty;
/**
* Friendica extension of the Smarty3 template engine
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class FriendicaSmarty extends Smarty
{
@ -38,8 +38,8 @@ class FriendicaSmarty extends Smarty
$this->setConfigDir('view/smarty3/config/');
$this->setCacheDir('view/smarty3/cache/');
$this->left_delimiter = $a->get_template_ldelim('smarty3');
$this->right_delimiter = $a->get_template_rdelim('smarty3');
$this->left_delimiter = $a->getTemplateLeftDelimiter('smarty3');
$this->right_delimiter = $a->getTemplateRightDelimiter('smarty3');
// Don't report errors so verbosely
$this->error_reporting = E_ALL & ~E_NOTICE;

View file

@ -9,7 +9,7 @@ use Friendica\Core\Addon;
/**
* Smarty implementation of the Friendica template engine interface
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
class FriendicaSmartyEngine implements ITemplateEngine
{

View file

@ -49,7 +49,7 @@ class Emailer
.rand(10000, 99999);
// generate a multipart/alternative message header
$messageHeader = $params['additionalMailHeader'] .
$messageHeader = defaults($params, 'additionalMailHeader', '') .
"From: $fromName <{$params['fromEmail']}>\n" .
"Reply-To: $fromName <{$params['replyTo']}>\n" .
"MIME-Version: 1.0\n" .

View file

@ -141,7 +141,7 @@ class ExAuth
$sUser = str_replace(['%20', '(a)'], [' ', '@'], $aCommand[1]);
// Does the hostname match? So we try directly
if ($a->get_hostname() == $aCommand[2]) {
if ($a->getHostName() == $aCommand[2]) {
$this->writeLog(LOG_INFO, 'internal user check for ' . $sUser . '@' . $aCommand[2]);
$found = DBA::exists('user', ['nickname' => $sUser]);
} else {
@ -179,17 +179,17 @@ class ExAuth
$url = ($ssl ? 'https' : 'http') . '://' . $host . '/noscrape/' . $user;
$data = Network::curl($url);
$curlResult = Network::curl($url);
if (!is_array($data)) {
if (!$curlResult->isSuccess()) {
return false;
}
if ($data['return_code'] != '200') {
if ($curlResult->getReturnCode() != 200) {
return false;
}
$json = @json_decode($data['body']);
$json = @json_decode($curlResult->getBody());
if (!is_object($json)) {
return false;
}
@ -221,7 +221,7 @@ class ExAuth
$sUser = str_replace(['%20', '(a)'], [' ', '@'], $aCommand[1]);
// Does the hostname match? So we try directly
if ($a->get_hostname() == $aCommand[2]) {
if ($a->getHostName() == $aCommand[2]) {
$this->writeLog(LOG_INFO, 'internal auth for ' . $sUser . '@' . $aCommand[2]);
$aUser = DBA::selectFirst('user', ['uid', 'password', 'legacy_password'], ['nickname' => $sUser]);

View file

@ -5,94 +5,64 @@
*/
namespace Friendica\Util;
use Friendica\BaseObject;
use Friendica\Core\Config;
use Friendica\Database\DBA;
use Friendica\Model\User;
use Friendica\Model\APContact;
use Friendica\Protocol\ActivityPub;
/**
* @brief Implements HTTP Signatures per draft-cavage-http-signatures-07.
*
* Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/Zotlabs/Web/HTTPSig.php
*
* Other parts of the code for HTTP signing are taken from the Osada project.
* https://framagit.org/macgirvin/osada
*
* @see https://tools.ietf.org/html/draft-cavage-http-signatures-07
*/
class HTTPSignature
{
/**
* @brief RFC5843
*
* Disabled until Friendica's ActivityPub implementation
* is ready.
*
* @see https://tools.ietf.org/html/rfc5843
*
* @param string $body The value to create the digest for
* @param boolean $set (optional, default true)
* If set send a Digest HTTP header
*
* @return string The generated digest of $body
*/
// public static function generateDigest($body, $set = true)
// {
// $digest = base64_encode(hash('sha256', $body, true));
//
// if($set) {
// header('Digest: SHA-256=' . $digest);
// }
// return $digest;
// }
// See draft-cavage-http-signatures-08
public static function verify($data, $key = '')
/**
* @brief Verifies a magic request
*
* @param $key
*
* @return array with verification data
*/
public static function verifyMagic($key)
{
$body = $data;
$headers = null;
$spoofable = false;
$result = [
'signer' => '',
'header_signed' => false,
'header_valid' => false,
'content_signed' => false,
'content_valid' => false
'header_valid' => false
];
// Decide if $data arrived via controller submission or curl.
if (is_array($data) && $data['header']) {
if (!$data['success']) {
return $result;
}
$headers = [];
$headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']).' '.$_SERVER['REQUEST_URI'];
$h = new HTTPHeaders($data['header']);
$headers = $h->fetch();
$body = $data['body'];
} else {
$headers = [];
$headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']).' '.$_SERVER['REQUEST_URI'];
foreach ($_SERVER as $k => $v) {
if (strpos($k, 'HTTP_') === 0) {
$field = str_replace('_', '-', strtolower(substr($k, 5)));
$headers[$field] = $v;
}
foreach ($_SERVER as $k => $v) {
if (strpos($k, 'HTTP_') === 0) {
$field = str_replace('_', '-', strtolower(substr($k, 5)));
$headers[$field] = $v;
}
}
$sig_block = null;
if (array_key_exists('signature', $headers)) {
$sig_block = self::parseSigheader($headers['signature']);
} elseif (array_key_exists('authorization', $headers)) {
$sig_block = self::parseSigheader($headers['authorization']);
}
$sig_block = self::parseSigheader($headers['authorization']);
if (!$sig_block) {
logger('no signature provided.');
return $result;
}
// Warning: This log statement includes binary data
// logger('sig_block: ' . print_r($sig_block,true), LOGGER_DATA);
$result['header_signed'] = true;
$signed_headers = $sig_block['headers'];
@ -112,24 +82,14 @@ class HTTPSignature
$signed_data = rtrim($signed_data, "\n");
$algorithm = null;
if ($sig_block['algorithm'] === 'rsa-sha256') {
$algorithm = 'sha256';
}
if ($sig_block['algorithm'] === 'rsa-sha512') {
$algorithm = 'sha512';
}
$algorithm = 'sha512';
if ($key && function_exists($key)) {
$result['signer'] = $sig_block['keyId'];
$key = $key($sig_block['keyId']);
}
// We don't use Activity Pub at the moment.
// if (!$key) {
// $result['signer'] = $sig_block['keyId'];
// $key = self::getActivitypubKey($sig_block['keyId']);
// }
logger('Got keyID ' . $sig_block['keyId']);
if (!$key) {
return $result;
@ -147,130 +107,39 @@ class HTTPSignature
$result['header_valid'] = true;
}
if (in_array('digest', $signed_headers)) {
$result['content_signed'] = true;
$digest = explode('=', $headers['digest']);
if ($digest[0] === 'SHA-256') {
$hashalg = 'sha256';
}
if ($digest[0] === 'SHA-512') {
$hashalg = 'sha512';
}
// The explode operation will have stripped the '=' padding, so compare against unpadded base64.
if (rtrim(base64_encode(hash($hashalg, $body, true)), '=') === $digest[1]) {
$result['content_valid'] = true;
}
}
logger('Content_Valid: ' . $result['content_valid']);
return $result;
}
/**
* Fetch the public key for Activity Pub contact.
*
* @param string|int The identifier (contact addr or contact ID).
* @return string|boolean The public key or false on failure.
*/
private static function getActivitypubKey($id)
{
if (strpos($id, 'acct:') === 0) {
$contact = DBA::selectFirst('contact', ['pubkey'], ['uid' => 0, 'addr' => str_replace('acct:', '', $id)]);
} else {
$contact = DBA::selectFirst('contact', ['pubkey'], ['id' => $id, 'network' => 'activitypub']);
}
if (DBA::isResult($contact)) {
return $contact['pubkey'];
}
if(function_exists('as_fetch')) {
$r = as_fetch($id);
}
if ($r) {
$j = json_decode($r, true);
if (array_key_exists('publicKey', $j) && array_key_exists('publicKeyPem', $j['publicKey'])) {
if ((array_key_exists('id', $j['publicKey']) && $j['publicKey']['id'] !== $id) && $j['id'] !== $id) {
return false;
}
return $j['publicKey']['publicKeyPem'];
}
}
return false;
}
/**
* @brief
*
* @param string $request
* @param array $head
* @param string $prvkey
* @param string $keyid (optional, default 'Key')
* @param boolean $send_headers (optional, default false)
* If set send a HTTP header
* @param boolean $auth (optional, default false)
* @param string $alg (optional, default 'sha256')
* @param string $crypt_key (optional, default null)
* @param string $crypt_algo (optional, default 'aes256ctr')
*
* @return array
*/
public static function createSig($request, $head, $prvkey, $keyid = 'Key', $send_headers = false, $auth = false, $alg = 'sha256', $crypt_key = null, $crypt_algo = 'aes256ctr')
public static function createSig($head, $prvkey, $keyid = 'Key')
{
$return_headers = [];
if ($alg === 'sha256') {
$algorithm = 'rsa-sha256';
}
$alg = 'sha512';
$algorithm = 'rsa-sha512';
if ($alg === 'sha512') {
$algorithm = 'rsa-sha512';
}
$x = self::sign($request, $head, $prvkey, $alg);
$x = self::sign($head, $prvkey, $alg);
$headerval = 'keyId="' . $keyid . '",algorithm="' . $algorithm
. '",headers="' . $x['headers'] . '",signature="' . $x['signature'] . '"';
if ($crypt_key) {
$x = Crypto::encapsulate($headerval, $crypt_key, $crypt_algo);
$headerval = 'iv="' . $x['iv'] . '",key="' . $x['key'] . '",alg="' . $x['alg'] . '",data="' . $x['data'] . '"';
}
if ($auth) {
$sighead = 'Authorization: Signature ' . $headerval;
} else {
$sighead = 'Signature: ' . $headerval;
}
$sighead = 'Authorization: Signature ' . $headerval;
if ($head) {
foreach ($head as $k => $v) {
if ($send_headers) {
// This is for ActivityPub implementation.
// Since the Activity Pub implementation isn't
// ready at the moment, we comment it out.
// header($k . ': ' . $v);
} else {
$return_headers[] = $k . ': ' . $v;
}
$return_headers[] = $k . ': ' . $v;
}
}
if ($send_headers) {
// This is for ActivityPub implementation.
// Since the Activity Pub implementation isn't
// ready at the moment, we comment it out.
// header($sighead);
} else {
$return_headers[] = $sighead;
}
$return_headers[] = $sighead;
return $return_headers;
}
@ -278,35 +147,27 @@ class HTTPSignature
/**
* @brief
*
* @param string $request
* @param array $head
* @param string $prvkey
* @param string $alg (optional) default 'sha256'
*
* @return array
*/
private static function sign($request, $head, $prvkey, $alg = 'sha256')
private static function sign($head, $prvkey, $alg = 'sha256')
{
$ret = [];
$headers = '';
$fields = '';
if ($request) {
$headers = '(request-target)' . ': ' . trim($request) . "\n";
$fields = '(request-target)';
}
if ($head) {
foreach ($head as $k => $v) {
$headers .= strtolower($k) . ': ' . trim($v) . "\n";
if ($fields) {
$fields .= ' ';
}
$fields .= strtolower($k);
foreach ($head as $k => $v) {
$headers .= strtolower($k) . ': ' . trim($v) . "\n";
if ($fields) {
$fields .= ' ';
}
// strip the trailing linefeed
$headers = rtrim($headers, "\n");
$fields .= strtolower($k);
}
// strip the trailing linefeed
$headers = rtrim($headers, "\n");
$sig = base64_encode(Crypto::rsaSign($headers, $prvkey, $alg));
@ -403,4 +264,177 @@ class HTTPSignature
return '';
}
/*
* Functions for ActivityPub
*/
/**
* @brief Transmit given data to a target for a user
*
* @param $data
* @param $target
* @param $uid
*/
public static function transmit($data, $target, $uid)
{
$owner = User::getOwnerDataById($uid);
if (!$owner) {
return;
}
$content = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
// Header data that is about to be signed.
$host = parse_url($target, PHP_URL_HOST);
$path = parse_url($target, PHP_URL_PATH);
$digest = 'SHA-256=' . base64_encode(hash('sha256', $content, true));
$content_length = strlen($content);
$headers = ['Content-Length: ' . $content_length, 'Digest: ' . $digest, 'Host: ' . $host];
$signed_data = "(request-target): post " . $path . "\ncontent-length: " . $content_length . "\ndigest: " . $digest . "\nhost: " . $host;
$signature = base64_encode(Crypto::rsaSign($signed_data, $owner['uprvkey'], 'sha256'));
$headers[] = 'Signature: keyId="' . $owner['url'] . '#main-key' . '",algorithm="rsa-sha256",headers="(request-target) content-length digest host",signature="' . $signature . '"';
$headers[] = 'Content-Type: application/activity+json';
$postResult = Network::post($target, $content, $headers);
logger('Transmit to ' . $target . ' returned ' . $postResult->getReturnCode());
}
/**
* @brief Gets a signer from a given HTTP request
*
* @param $content
* @param $http_headers
*
* @return signer string
*/
public static function getSigner($content, $http_headers)
{
$object = json_decode($content, true);
if (empty($object)) {
return false;
}
$actor = JsonLD::fetchElement($object, 'actor', 'id');
$headers = [];
$headers['(request-target)'] = strtolower($http_headers['REQUEST_METHOD']) . ' ' . $http_headers['REQUEST_URI'];
// First take every header
foreach ($http_headers as $k => $v) {
$field = str_replace('_', '-', strtolower($k));
$headers[$field] = $v;
}
// Now add every http header
foreach ($http_headers as $k => $v) {
if (strpos($k, 'HTTP_') === 0) {
$field = str_replace('_', '-', strtolower(substr($k, 5)));
$headers[$field] = $v;
}
}
$sig_block = self::parseSigHeader($http_headers['HTTP_SIGNATURE']);
if (empty($sig_block) || empty($sig_block['headers']) || empty($sig_block['keyId'])) {
return false;
}
$signed_data = '';
foreach ($sig_block['headers'] as $h) {
if (array_key_exists($h, $headers)) {
$signed_data .= $h . ': ' . $headers[$h] . "\n";
}
}
$signed_data = rtrim($signed_data, "\n");
if (empty($signed_data)) {
return false;
}
$algorithm = null;
if ($sig_block['algorithm'] === 'rsa-sha256') {
$algorithm = 'sha256';
}
if ($sig_block['algorithm'] === 'rsa-sha512') {
$algorithm = 'sha512';
}
if (empty($algorithm)) {
return false;
}
$key = self::fetchKey($sig_block['keyId'], $actor);
if (empty($key)) {
return false;
}
if (!Crypto::rsaVerify($signed_data, $sig_block['signature'], $key['pubkey'], $algorithm)) {
return false;
}
// Check the digest when it is part of the signed data
if (in_array('digest', $sig_block['headers'])) {
$digest = explode('=', $headers['digest'], 2);
if ($digest[0] === 'SHA-256') {
$hashalg = 'sha256';
}
if ($digest[0] === 'SHA-512') {
$hashalg = 'sha512';
}
/// @todo add all hashes from the rfc
if (!empty($hashalg) && base64_encode(hash($hashalg, $content, true)) != $digest[1]) {
return false;
}
}
// Check the content-length when it is part of the signed data
if (in_array('content-length', $sig_block['headers'])) {
if (strlen($content) != $headers['content-length']) {
return false;
}
}
return $key['url'];
}
/**
* @brief fetches a key for a given id and actor
*
* @param $id
* @param $actor
*
* @return array with actor url and public key
*/
private static function fetchKey($id, $actor)
{
$url = (strpos($id, '#') ? substr($id, 0, strpos($id, '#')) : $id);
$profile = APContact::getByURL($url);
if (!empty($profile)) {
logger('Taking key from id ' . $id, LOGGER_DEBUG);
return ['url' => $url, 'pubkey' => $profile['pubkey']];
} elseif ($url != $actor) {
$profile = APContact::getByURL($actor);
if (!empty($profile)) {
logger('Taking key from actor ' . $actor, LOGGER_DEBUG);
return ['url' => $actor, 'pubkey' => $profile['pubkey']];
}
}
return false;
}
}

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