Merge branch 'master' into develop

This commit is contained in:
Hypolite Petovan 2019-06-23 17:40:52 -04:00
commit b9ab613777
164 changed files with 30233 additions and 26266 deletions

4
.gitignore vendored
View file

@ -6,7 +6,6 @@ favicon.*
*.out
*.version*
home.html
*~
robots.txt
@ -74,3 +73,6 @@ venv/
#ignore filesystem storage default path
/storage
#Ignore log folder
/log

View file

@ -1,17 +1,20 @@
Version 2019.06 (UNRELEASED) (2019-06-?)
Version 2019.06 (2019-06-23)
Friendica Core:
Update to the tranlation (CS, DE, ET, PL, PT-BR, SV) [translation teams]
Update to the documentation [nupplaphil, realkinetix]
Update to the tranlation (CS, DE, EN-GB, EN-US, ET, FR, IT, PL, PT-BR, SV) [translation teams]
Update to the documentation [nupplaphil, realkinetix, MrPetovan]
Update to the themes (frio, vier) [BinkaDroid, MrPetovan, tobiasd]
Enhancements to the API [annando, MrPetovan]
Enhancements to the way reshares are handled [annando]
Enhancements to the redis configuration [nupplaphil]
Enhancements to the federation stats display in the admin panel [tobiasd]
Enhancements to the processing of changed storage engine [MrPetovan]
Enhancements to ActivityPub support [annando, MrPetovan]
Enhancements to code security [MrPetovan]
Enhancements to delivery counter [annando]
Fixed the notification order [JeroenED]
Fixed the timezone of Friendica logs [nupplaphil]
Fixed tag completion painfully slow [AlfredSK]
Fixed a regression in notifications [MrPetovan]
Fixed a regression in notifications [MrPetovan, annando]
Fixed an issue with smilies and code blocks [MrPetovan]
Fixed an AP issue with unavailable local profiles [MrPetovan]
Fixed an issue with the File to Folder feature [MrPetovan]
@ -20,34 +23,55 @@ Version 2019.06 (UNRELEASED) (2019-06-?)
Fixed an issue occuring when the BasePath was not set [tobiasd]
Fixed an issue with additionally opened Sessions [MrPetovan]
Fixed an issue with legacy loglevel mapping [nupplaphil]
Fixed contact suggestions [annando]
Fixed an issue with frio hovercard [nupplaphil]
Fixed event interaction federation [annando]
Fixed remote image permission [deantownsley]
General Code cleaning and restructuring [annando, nupplaphil, tobiasd]
Added frio color scheme sharing [JeroenED]
Added syslog and stream Logger [nupplaphil]
Added storage move cronjob [MrPetovan]
Added collapsible panel for connector permission fields [MrPetovan]
Added rule-based router [MrPetovan]
Added Estinian translation [Rain Hawk]
Added Estonian translation [Rain Hawk]
Added APCu caching [nupplaphil]
Added BlockServer command to the Friendica console [nupplaphil]
Added reshare count [annando]
Added rule-based router [MrPetovan, nupplaphil]
Added themed error pages with mascot [MrPetovan, lostinlight]
Added contact relationship filter [MrPetovan]
Removed the old queue mechanism (deferred workers are now used) [annando]
Removed BasePath and Hostname settings from the admin panel [nupplaphil]
Remove support for defunct F-Droid Friendica app [MrPetovan]
Friendica Addons:
Update to the tranlation (ET, SV, ZH_CN) [translation teams]
botdetection:
Added a new addon for preventing access by bots [nupplaphil]
Added a new addon for preventing access by bots [nupplaphil, annando]
buffer:
Traces of Google+ were removed [annando]
curweather:
Fixed a problem with the display of the correct temperature unit [tobiasd]
fromgplus:
Deprecated the addon as Google+ was closed [tobiasd]
fortunate:
Deprecated addon for incompatibility with latest Friendica version [MrPetovan]
phpmailer:
Added a new addon to use external SMTP for email [M-arcus]
Added a new addon to use external SMTP for email [M-arcus, kecalcze, MrPetovan]
pledgie:
Deprecated addon as service was discontinued [M-arcus]
xmpp:
Marked addon as unsupported because of various incompatibilities with themes [MrPetovan]
Closed Issues:
5011, 5047, 5850, 6303, 6319, 6478, 6319, 6720, 6815, 6864, 6879,
6903, 6921, 6927, 6936, 6941, 6943, 6947, 6948, 6952
1012, 2209, 2528, 3309, 3717, 3816, 3869, 4453, 4999, 5011, 5047, 5276, 5850, 5983, 6303, 6319, 6379, 6410, 6477,
6478, 6720, 6799, 6813, 6819, 6861, 6864, 6879, 6903, 6916, 6917, 6918, 6921, 6927, 6929, 6936, 6938, 6941, 6943,
6947, 6948, 6950, 6952, 6983, 6999, 7023, 7036, 7047, 7106, 7112, 7119, 7128, 7130, 7131, 7141, 7142, 7150, 7171,
7183, 7196, 7209, 7223, 7226, 7240, 7241, 7249, 7264, 7269, 7271, 7275, 7300, 7303
Version 2019.04 (2019-04-28)
Friendica Core:
Fixed a privacy problem with postings accessed by feed [MrPetovan]
Version 2019.03 (2019-03-22)
Friendica Core:

View file

@ -1,3 +1,5 @@
23n
Abinoam P. Marques Jr.
Abraham Pérez Hernández
@ -26,6 +28,7 @@ Andy Hee
Angristan
Anthronaut
Arian - Cazare Muncitori
Asher Pen
Athalbert
aweiher
axelt
@ -37,6 +40,8 @@ Beluga
Ben
Ben Roberts
ben-utzer
BinkaDroid
Bjoessi
bufalo1973
Calango Jr
Carlos Solís
@ -107,6 +112,7 @@ Jens Tautenhahn
jensp
Jeroen De Meerleer
jeroenpraat
JOduMonT
Johannes Schwab
John Brazil
Jonatan Nyberg
@ -175,6 +181,7 @@ R C
Rabuzarus
Radek
Rafael Garau
Rain Hawk
Rainulf Pineda
Ralf Thees
Ralph
@ -183,6 +190,7 @@ rcmaniac
rebeka-catalina
repat
Ricardo Pereira
Rik 4
RJ Madsen
Roland Häder
Rui Andrada
@ -207,6 +215,7 @@ Steffen K9
StefOfficiel
Sveinn í Felli
Sven Anders
Sylke Vicious
Sylvain Lagacé
szymon.filip
Sérgio Lima
@ -225,6 +234,7 @@ tomacat
tomamplius
tomtom84
Tony Baldwin
Torbjörn Andersson
TORminator
trebor
tschlotfeldt
@ -234,6 +244,7 @@ U-SOUND\mike
ufic
Ulf Rompe
Unknown
Valvin
Vasudev Kamath
Vasya Novikov
Vinzenz Vietzke

View file

@ -1 +1 @@
2019.06-dev
2019.06

View file

@ -31,7 +31,7 @@ use Friendica\Util\DateTimeFormat;
define('FRIENDICA_PLATFORM', 'Friendica');
define('FRIENDICA_CODENAME', 'Dalmatian Bellflower');
define('FRIENDICA_VERSION', '2019.06-dev');
define('FRIENDICA_VERSION', '2019.06');
define('DFRN_PROTOCOL_VERSION', '2.23');
define('NEW_UPDATE_ROUTINE_VERSION', 1170);
@ -535,39 +535,6 @@ function is_site_admin()
return local_user() && $admin_email && in_array(defaults($a->user, 'email', ''), $adminlist);
}
/**
* @brief Returns querystring as string from a mapped array.
*
* @param array $params mapped array with query parameters
* @param string $name of parameter, default null
*
* @return string
*/
function build_querystring($params, $name = null)
{
$ret = "";
foreach ($params as $key => $val) {
if (is_array($val)) {
/// @TODO maybe not compare against null, use is_null()
if ($name == null) {
$ret .= build_querystring($val, $key);
} else {
$ret .= build_querystring($val, $name . "[$key]");
}
} else {
$val = urlencode($val);
/// @TODO maybe not compare against null, use is_null()
if ($name != null) {
/// @TODO two string concated, can be merged to one
$ret .= $name . "[$key]" . "=$val&";
} else {
$ret .= "$key=$val&";
}
}
}
return $ret;
}
function explode_querystring($query)
{
$arg_st = strpos($query, '?');

View file

@ -37,6 +37,7 @@
"mobiledetect/mobiledetectlib": "2.8.*",
"monolog/monolog": "^1.24",
"nikic/fast-route": "^1.3",
"paragonie/hidden-string": "^1.0",
"pear/text_languagedetect": "1.*",
"pragmarx/google2fa": "^5.0",
"pragmarx/recovery": "^0.1.0",
@ -46,6 +47,7 @@
"fxp/composer-asset-plugin": "~1.3",
"bower-asset/base64": "^1.0",
"bower-asset/chart-js": "^2.7",
"bower-asset/dompurify": "^1.0",
"bower-asset/perfect-scrollbar": "^0.6",
"bower-asset/vue": "^2.5",
"npm-asset/jquery": "^2.0",
@ -95,7 +97,19 @@
},
"archive": {
"exclude": [
"log", "cache", "/photo", "/proxy"
"/.*",
"/*file",
"!/.htaccess-dist",
"/tests",
"/*.xml",
"/composer.*",
"/log",
"/cache",
"/photo",
"/proxy",
"/addon",
"!/vendor",
"!/view/asset"
]
},
"require-dev": {

179
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "d7302553201de079b72871c0b2922ce7",
"content-hash": "eb985236d64ed0b0fe1fc2e4ac6616e2",
"packages": [
{
"name": "asika/simple-console",
@ -148,6 +148,51 @@
"description": "Base64 encoding and decoding",
"time": "2017-03-25T21:16:21+00:00"
},
{
"name": "bower-asset/dompurify",
"version": "1.0.10",
"source": {
"type": "git",
"url": "https://github.com/cure53/DOMPurify.git",
"reference": "b537cab466329b1b077e0e5e3c14edad2b7142f7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cure53/DOMPurify/zipball/b537cab466329b1b077e0e5e3c14edad2b7142f7",
"reference": "b537cab466329b1b077e0e5e3c14edad2b7142f7",
"shasum": ""
},
"type": "bower-asset-library",
"extra": {
"bower-asset-main": "src/purify.js",
"bower-asset-ignore": [
"**/.*",
"demos",
"scripts",
"test",
"website"
]
},
"license": [
"MPL-2.0",
"Apache-2.0"
],
"description": "A DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG",
"keywords": [
"cross site scripting",
"dom",
"filter",
"html",
"mathml",
"sanitize",
"sanitizer",
"secure",
"security",
"svg",
"xss"
],
"time": "2019-02-19T13:27:01+00:00"
},
{
"name": "bower-asset/perfect-scrollbar",
"version": "0.6.16",
@ -1175,6 +1220,22 @@
"require": {
"npm-asset/ev-emitter": ">=1.0.0,<2.0.0"
},
"require-dev": {
"npm-asset/chalk": ">=1.1.1,<2.0.0",
"npm-asset/cheerio": ">=0.19.0,<0.20.0",
"npm-asset/gulp": ">=3.9.0,<4.0.0",
"npm-asset/gulp-jshint": ">=1.11.2,<2.0.0",
"npm-asset/gulp-json-lint": ">=0.1.0,<0.2.0",
"npm-asset/gulp-rename": ">=1.2.2,<2.0.0",
"npm-asset/gulp-replace": ">=0.5.4,<0.6.0",
"npm-asset/gulp-requirejs-optimize": "dev-github:metafizzy/gulp-requirejs-optimize",
"npm-asset/gulp-uglify": ">=1.4.2,<2.0.0",
"npm-asset/gulp-util": ">=3.0.7,<4.0.0",
"npm-asset/highlight.js": ">=8.9.1,<9.0.0",
"npm-asset/marked": ">=0.3.5,<0.4.0",
"npm-asset/minimist": ">=1.2.0,<2.0.0",
"npm-asset/transfob": ">=1.0.0,<2.0.0"
},
"type": "npm-asset-library",
"extra": {
"npm-asset-bugs": {
@ -1220,6 +1281,14 @@
"reference": null,
"shasum": "2736e332aaee73ccf0a14a5f0066391a0a13f4a3"
},
"require-dev": {
"npm-asset/grunt": "~0.4.2",
"npm-asset/grunt-contrib-cssmin": "~0.9.0",
"npm-asset/grunt-contrib-jshint": "~0.6.3",
"npm-asset/grunt-contrib-less": "~0.11.0",
"npm-asset/grunt-contrib-uglify": "~0.4.0",
"npm-asset/grunt-contrib-watch": "~0.6.1"
},
"type": "npm-asset-library",
"extra": {
"npm-asset-bugs": {
@ -1253,6 +1322,32 @@
"reference": null,
"shasum": "2c89d6889b5eac522a7eea32c14521559c6cbf02"
},
"require-dev": {
"npm-asset/commitplease": "2.0.0",
"npm-asset/core-js": "0.9.17",
"npm-asset/grunt": "0.4.5",
"npm-asset/grunt-babel": "5.0.1",
"npm-asset/grunt-cli": "0.1.13",
"npm-asset/grunt-compare-size": "0.4.0",
"npm-asset/grunt-contrib-jshint": "0.11.2",
"npm-asset/grunt-contrib-uglify": "0.9.2",
"npm-asset/grunt-contrib-watch": "0.6.1",
"npm-asset/grunt-git-authors": "2.0.1",
"npm-asset/grunt-jscs": "2.1.0",
"npm-asset/grunt-jsonlint": "1.0.4",
"npm-asset/grunt-npmcopy": "0.1.0",
"npm-asset/gzip-js": "0.3.2",
"npm-asset/jsdom": "5.6.1",
"npm-asset/load-grunt-tasks": "1.0.0",
"npm-asset/qunit-assert-step": "1.0.3",
"npm-asset/qunitjs": "1.17.1",
"npm-asset/requirejs": "2.1.17",
"npm-asset/sinon": "1.10.3",
"npm-asset/sizzle": "2.2.1",
"npm-asset/strip-json-comments": "1.0.3",
"npm-asset/testswarm": "1.1.0",
"npm-asset/win-spawn": "2.0.0"
},
"type": "npm-asset-library",
"extra": {
"npm-asset-bugs": {
@ -1403,6 +1498,12 @@
"reference": null,
"shasum": "06f0335f16e353a695e7206bf50503cb523a6ee5"
},
"require-dev": {
"npm-asset/grunt": "~0.4.1",
"npm-asset/grunt-contrib-connect": "~0.5.0",
"npm-asset/grunt-contrib-jshint": "~0.7.1",
"npm-asset/grunt-contrib-uglify": "~0.2.7"
},
"type": "npm-asset-library",
"extra": {
"npm-asset-bugs": {
@ -1622,25 +1723,24 @@
},
{
"name": "paragonie/constant_time_encoding",
"version": "v1.0.4",
"version": "v2.2.3",
"source": {
"type": "git",
"url": "https://github.com/paragonie/constant_time_encoding.git",
"reference": "2132f0f293d856026d7d11bd81b9f4a23a1dc1f6"
"reference": "55af0dc01992b4d0da7f6372e2eac097bbbaffdb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/2132f0f293d856026d7d11bd81b9f4a23a1dc1f6",
"reference": "2132f0f293d856026d7d11bd81b9f4a23a1dc1f6",
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/55af0dc01992b4d0da7f6372e2eac097bbbaffdb",
"reference": "55af0dc01992b4d0da7f6372e2eac097bbbaffdb",
"shasum": ""
},
"require": {
"php": "^5.3|^7"
"php": "^7"
},
"require-dev": {
"paragonie/random_compat": "^1.4|^2",
"phpunit/phpunit": "4.*|5.*",
"vimeo/psalm": "^0.3|^1"
"phpunit/phpunit": "^6|^7",
"vimeo/psalm": "^1|^2"
},
"type": "library",
"autoload": {
@ -1681,7 +1781,56 @@
"hex2bin",
"rfc4648"
],
"time": "2018-04-30T17:57:16+00:00"
"time": "2019-01-03T20:26:31+00:00"
},
{
"name": "paragonie/hidden-string",
"version": "v1.0.0",
"source": {
"type": "git",
"url": "https://github.com/paragonie/hidden-string.git",
"reference": "0bbb00be0e33b8e1d48fa79ea35cd42d3091a936"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/hidden-string/zipball/0bbb00be0e33b8e1d48fa79ea35cd42d3091a936",
"reference": "0bbb00be0e33b8e1d48fa79ea35cd42d3091a936",
"shasum": ""
},
"require": {
"paragonie/constant_time_encoding": "^2",
"paragonie/sodium_compat": "^1.6",
"php": "^7"
},
"require-dev": {
"phpunit/phpunit": "^6|^7",
"vimeo/psalm": "^1"
},
"type": "library",
"autoload": {
"psr-4": {
"ParagonIE\\HiddenString\\": "./src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MPL-2.0"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "info@paragonie.com",
"homepage": "https://paragonie.com"
}
],
"description": "Encapsulate strings in an object to hide them from stack traces",
"homepage": "https://github.com/paragonie/hidden-string",
"keywords": [
"hidden",
"stack trace",
"string"
],
"time": "2018-05-07T20:28:06+00:00"
},
{
"name": "paragonie/random_compat",
@ -3600,7 +3749,7 @@
}
],
"description": "Provides the functionality to compare PHP values for equality",
"homepage": "http://www.github.com/sebastianbergmann/comparator",
"homepage": "https://github.com/sebastianbergmann/comparator",
"keywords": [
"comparator",
"compare",
@ -3702,7 +3851,7 @@
}
],
"description": "Provides functionality to handle HHVM/PHP environments",
"homepage": "http://www.github.com/sebastianbergmann/environment",
"homepage": "https://github.com/sebastianbergmann/environment",
"keywords": [
"Xdebug",
"environment",
@ -3770,7 +3919,7 @@
}
],
"description": "Provides the functionality to export PHP variables for visualization",
"homepage": "http://www.github.com/sebastianbergmann/exporter",
"homepage": "https://github.com/sebastianbergmann/exporter",
"keywords": [
"export",
"exporter"
@ -3822,7 +3971,7 @@
}
],
"description": "Snapshotting of global state",
"homepage": "http://www.github.com/sebastianbergmann/global-state",
"homepage": "https://github.com/sebastianbergmann/global-state",
"keywords": [
"global state"
],
@ -3924,7 +4073,7 @@
}
],
"description": "Provides functionality to recursively process PHP variables",
"homepage": "http://www.github.com/sebastianbergmann/recursion-context",
"homepage": "https://github.com/sebastianbergmann/recursion-context",
"time": "2016-11-19T07:33:16+00:00"
},
{

View file

@ -74,7 +74,7 @@ return [
// logfile (String)
// The logfile for storing logs.
// Can be a full path or a relative path to the Friendica home directory
'logfile' => 'friendica.log',
'logfile' => 'log/friendica.log',
// loglevel (String)
// The loglevel for all logs.

View file

@ -358,6 +358,7 @@ Called from `Emailer::send()` before building the mime message.
- **htmlVersion**: html version of the message
- **textVersion**: text only version of the message
- **additionalMailHeader**: additions to the smtp mail header
- **sent**: default false, if set to true in the hook, the default mailer will be skipped.
### emailer_send
Called before calling PHP's `mail()`.
@ -367,6 +368,7 @@ Called before calling PHP's `mail()`.
- **subject**
- **body**
- **headers**
- **sent**: default false, if set to true in the hook, the default mailer will be skipped.
### load_config
Called during `App` initialization to allow addons to load their own configuration file(s) with `App::loadConfigFile()`.

View file

@ -126,16 +126,15 @@ A **hidden contact** will not be displayed in any "friend list" (except to you).
However a hidden contact will appear normally in conversations and this may expose his/her hidden status to anybody who can see the conversation.
<a name="removed"></a>
### What happens when an account is removed? Is it truly deleted?
### What happens when an account is removed?
If you delete your account, we will immediately remove all your content on **your** server.
If you remove your account, it will be scheduled for permanent deletion in *seven days*.
As soon as you activate the deletion process you won't be able to login any more.
Only the administrator of your node can halt this process prior to permanent deletion.
Then Friendica issues requests to all your contacts to remove you.
This will also remove you from the global directory.
Doing this requires your account and profile still to be "partially" available for up to 24 hours in order to establish contact with all your friends.
We can block it in several ways so that it appears empty and all profile information erased, but will then wait for 24 hours (or after all of your contacts have been notified) before we can physically remove it.
After that, your account is deleted.
After the elapsed time of seven days, all your posts, messages, photos, and personal information stored on your node will be deleted.
Your node will also issue removal requests to all your contacts; this will also remove your profile from the global directory if you are listed.
Your username cannot be reissued for future sign-ups for security reasons.
<a name="hashtag"></a>
### Can I follow a hashtag?

View file

@ -361,7 +361,7 @@ function api_call(App $a)
}
}
Logger::warning(API_LOG_PREFIX . 'not implemented', ['module' => 'api', 'action' => 'call']);
Logger::warning(API_LOG_PREFIX . 'not implemented', ['module' => 'api', 'action' => 'call', 'query' => $a->query_string]);
throw new NotImplementedException();
} catch (HTTPException $e) {
header("HTTP/1.1 {$e->getCode()} {$e->httpdesc}");
@ -611,7 +611,7 @@ function api_get_user(App $a, $contact_id = null)
'name' => $contact["name"],
'screen_name' => (($contact['nick']) ? $contact['nick'] : $contact['name']),
'location' => ($contact["location"] != "") ? $contact["location"] : ContactSelector::networkToName($contact['network'], $contact['url']),
'description' => $contact["about"],
'description' => HTML::toPlaintext(BBCode::toPlaintext($contact["about"])),
'profile_image_url' => $contact["micro"],
'profile_image_url_https' => $contact["micro"],
'profile_image_url_profile_size' => $contact["thumb"],
@ -690,7 +690,7 @@ function api_get_user(App $a, $contact_id = null)
'name' => (($uinfo[0]['name']) ? $uinfo[0]['name'] : $uinfo[0]['nick']),
'screen_name' => (($uinfo[0]['nick']) ? $uinfo[0]['nick'] : $uinfo[0]['name']),
'location' => $location,
'description' => $description,
'description' => HTML::toPlaintext(BBCode::toPlaintext($description)),
'profile_image_url' => $uinfo[0]['micro'],
'profile_image_url_https' => $uinfo[0]['micro'],
'profile_image_url_profile_size' => $uinfo[0]["thumb"],
@ -1271,7 +1271,7 @@ function api_status_show($type, $item_id)
function api_get_last_status($ownerId, $uid)
{
$condition = [
'owner-id' => $ownerId,
'author-id'=> $ownerId,
'uid' => $uid,
'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT],
'private' => false

View file

@ -446,80 +446,3 @@ function drop_item($id, $return = '')
//NOTREACHED
}
}
/* arrange the list in years */
function list_post_dates($uid, $wall)
{
$dnow = DateTimeFormat::localNow('Y-m-d');
$dthen = Item::firstPostDate($uid, $wall);
if (!$dthen) {
return [];
}
// Set the start and end date to the beginning of the month
$dnow = substr($dnow, 0, 8) . '01';
$dthen = substr($dthen, 0, 8) . '01';
$ret = [];
/*
* Starting with the current month, get the first and last days of every
* month down to and including the month of the first post
*/
while (substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
$dyear = intval(substr($dnow, 0, 4));
$dstart = substr($dnow, 0, 8) . '01';
$dend = substr($dnow, 0, 8) . Temporal::getDaysInMonth(intval($dnow), intval(substr($dnow, 5)));
$start_month = DateTimeFormat::utc($dstart, 'Y-m-d');
$end_month = DateTimeFormat::utc($dend, 'Y-m-d');
$str = L10n::getDay(DateTimeFormat::utc($dnow, 'F'));
if (empty($ret[$dyear])) {
$ret[$dyear] = [];
}
$ret[$dyear][] = [$str, $end_month, $start_month];
$dnow = DateTimeFormat::utc($dnow . ' -1 month', 'Y-m-d');
}
return $ret;
}
function posted_date_widget($url, $uid, $wall)
{
$o = '';
if (!Feature::isEnabled($uid, 'archives')) {
return $o;
}
// For former Facebook folks that left because of "timeline"
/*
* @TODO old-lost code?
if ($wall && intval(PConfig::get($uid, 'system', 'no_wall_archive_widget')))
return $o;
*/
$visible_years = PConfig::get($uid, 'system', 'archive_visible_years', 5);
$ret = list_post_dates($uid, $wall);
if (!DBA::isResult($ret)) {
return $o;
}
$cutoff_year = intval(DateTimeFormat::localNow('Y')) - $visible_years;
$cutoff = ((array_key_exists($cutoff_year, $ret))? true : false);
$o = Renderer::replaceMacros(Renderer::getMarkupTemplate('posted_date_widget.tpl'),[
'$title' => L10n::t('Archives'),
'$size' => $visible_years,
'$cutoff_year' => $cutoff_year,
'$cutoff' => $cutoff,
'$url' => $url,
'$dates' => $ret,
'$showmore' => L10n::t('show more')
]);
return $o;
}

View file

@ -8,10 +8,8 @@ use Friendica\Content\Smilies;
use Friendica\Content\Text\BBCode;
use Friendica\Core\Protocol;
use Friendica\Model\Contact;
use Friendica\Model\FileTag;
use Friendica\Util\Strings;
use Friendica\Util\XML;
/**
* Turn user/group ACLs stored as angle bracketed text into arrays
@ -186,21 +184,17 @@ function get_cats_and_terms($item)
{
$categories = [];
$folders = [];
$matches = [];
$first = true;
$cnt = preg_match_all('/<(.*?)>/', $item['file'], $matches, PREG_SET_ORDER);
if ($cnt) {
foreach ($matches as $mtch) {
$categories[] = [
'name' => XML::escape(FileTag::decode($mtch[1])),
'url' => "#",
'removeurl' => ((local_user() == $item['uid'])?'filerm/' . $item['id'] . '?f=&cat=' . XML::escape(FileTag::decode($mtch[1])):""),
'first' => $first,
'last' => false
];
$first = false;
}
foreach (FileTag::fileToArray($item['file'] ?? '', 'category') as $savedFolderName) {
$categories[] = [
'name' => $savedFolderName,
'url' => "#",
'removeurl' => ((local_user() == $item['uid']) ? 'filerm/' . $item['id'] . '?f=&cat=' . rawurlencode($savedFolderName) : ""),
'first' => $first,
'last' => false
];
$first = false;
}
if (count($categories)) {
@ -208,20 +202,15 @@ function get_cats_and_terms($item)
}
if (local_user() == $item['uid']) {
$matches = [];
$first = true;
$cnt = preg_match_all('/\[(.*?)\]/', $item['file'], $matches, PREG_SET_ORDER);
if ($cnt) {
foreach ($matches as $mtch) {
$folders[] = [
'name' => XML::escape(FileTag::decode($mtch[1])),
'url' => "#",
'removeurl' => ((local_user() == $item['uid']) ? 'filerm/' . $item['id'] . '?f=&term=' . XML::escape(FileTag::decode($mtch[1])) : ""),
'first' => $first,
'last' => false
];
$first = false;
}
foreach (FileTag::fileToArray($item['file'] ?? '') as $savedFolderName) {
$folders[] = [
'name' => $savedFolderName,
'url' => "#",
'removeurl' => ((local_user() == $item['uid']) ? 'filerm/' . $item['id'] . '?f=&term=' . rawurlencode($savedFolderName) : ""),
'first' => $first,
'last' => false
];
$first = false;
}
}

View file

@ -147,7 +147,7 @@ function cal_content(App $a)
$sql_extra = " AND `event`.`cid` = 0 " . $sql_perms;
// get the tab navigation bar
$tabs = Profile::getTabs($a, false, $a->data['user']['nickname']);
$tabs = Profile::getTabs($a, 'cal', false, $a->data['user']['nickname']);
// The view mode part is similiar to /mod/events.php
if ($mode == 'view') {

View file

@ -209,7 +209,7 @@ function dfrn_confirm_post(App $a, $handsfree = null)
*
*/
$res = Network::post($dfrn_confirm, $params, null, $redirects, 120)->getBody();
$res = Network::post($dfrn_confirm, $params, [], 120)->getBody();
Logger::log(' Confirm: received data: ' . $res, Logger::DATA);

View file

@ -190,13 +190,13 @@ function dfrn_dispatch_public($postdata)
}
// Fetch the corresponding public contact
$contact = Contact::getDetailsByAddr($msg['author'], 0);
if (!$contact) {
$contact_id = Contact::getIdForURL($msg['author']);
if (empty($contact_id)) {
Logger::log('Contact not found for address ' . $msg['author']);
System::xmlExit(3, 'Contact ' . $msg['author'] . ' not found');
}
$importer = DFRN::getImporter($contact['id']);
$importer = DFRN::getImporter($contact_id);
// This should never fail
if (empty($importer)) {

View file

@ -476,7 +476,7 @@ function dfrn_request_post(App $a)
function dfrn_request_content(App $a)
{
if (($a->argc != 2) || (!count($a->profile))) {
if ($a->argc != 2 || empty($a->profile)) {
return "";
}

View file

@ -1,265 +0,0 @@
<?php
/**
* @file mod/dirfind.php
*/
use Friendica\App;
use Friendica\Content\ContactSelector;
use Friendica\Content\Pager;
use Friendica\Content\Widget;
use Friendica\Core\Config;
use Friendica\Core\L10n;
use Friendica\Core\Protocol;
use Friendica\Core\Renderer;
use Friendica\Core\System;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\Model;
use Friendica\Module;
use Friendica\Network\Probe;
use Friendica\Protocol\PortableContact;
use Friendica\Util\Network;
use Friendica\Util\Proxy as ProxyUtils;
use Friendica\Util\Strings;
function dirfind_init(App $a) {
if (! local_user()) {
notice(L10n::t('Permission denied.') . EOL );
return;
}
if (empty($a->page['aside'])) {
$a->page['aside'] = '';
}
$a->page['aside'] .= Widget::findPeople();
$a->page['aside'] .= Widget::follow();
}
function dirfind_content(App $a, $prefix = "") {
$community = false;
$discover_user = false;
$local = Config::get('system','poco_local_search');
$search = $prefix.Strings::escapeTags(trim(defaults($_REQUEST, 'search', '')));
$header = '';
if (strpos($search,'@') === 0) {
$search = substr($search,1);
$header = L10n::t('People Search - %s', $search);
if ((filter_var($search, FILTER_VALIDATE_EMAIL) && Network::isEmailDomainValid($search)) ||
(substr(Strings::normaliseLink($search), 0, 7) == "http://")) {
$user_data = Probe::uri($search);
$discover_user = (in_array($user_data["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA]));
}
}
if (strpos($search,'!') === 0) {
$search = substr($search,1);
$community = true;
$header = L10n::t('Forum Search - %s', $search);
}
$o = '';
if ($search) {
$pager = new Pager($a->query_string);
if ($discover_user) {
$j = new stdClass();
$j->total = 1;
$j->items_page = 1;
$j->page = $pager->getPage();
$objresult = new stdClass();
$objresult->cid = 0;
$objresult->name = $user_data["name"];
$objresult->addr = $user_data["addr"];
$objresult->url = $user_data["url"];
$objresult->photo = $user_data["photo"];
$objresult->tags = "";
$objresult->network = $user_data["network"];
$contact = Model\Contact::getDetailsByURL($user_data["url"], local_user());
$objresult->cid = $contact["cid"];
$objresult->pcid = $contact["zid"];
$j->results[] = $objresult;
// 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)) {
Model\GContact::update($user_data);
}
} elseif ($local) {
if ($community) {
$extra_sql = " AND `community`";
} else {
$extra_sql = "";
}
$pager->setItemsPerPage(80);
if (Config::get('system','diaspora_enabled')) {
$diaspora = Protocol::DIASPORA;
} else {
$diaspora = Protocol::DFRN;
}
if (!Config::get('system','ostatus_disabled')) {
$ostatus = Protocol::OSTATUS;
} else {
$ostatus = Protocol::DFRN;
}
$search2 = "%".$search."%";
/// @TODO These 2 SELECTs are not checked on validity with DBA::isResult()
$count = q("SELECT count(*) AS `total` FROM `gcontact`
WHERE NOT `hide` AND `network` IN ('%s', '%s', '%s', '%s') AND
((`last_contact` >= `last_failure`) OR (`updated` >= `last_failure`)) AND
(`url` LIKE '%s' OR `name` LIKE '%s' OR `location` LIKE '%s' OR
`addr` LIKE '%s' OR `about` LIKE '%s' OR `keywords` LIKE '%s') $extra_sql",
DBA::escape(Protocol::ACTIVITYPUB), DBA::escape(Protocol::DFRN), DBA::escape($ostatus), DBA::escape($diaspora),
DBA::escape(Strings::escapeHtml($search2)), DBA::escape(Strings::escapeHtml($search2)), DBA::escape(Strings::escapeHtml($search2)),
DBA::escape(Strings::escapeHtml($search2)), DBA::escape(Strings::escapeHtml($search2)), DBA::escape(Strings::escapeHtml($search2)));
$results = q("SELECT `nurl`
FROM `gcontact`
WHERE NOT `hide` AND `network` IN ('%s', '%s', '%s', '%s') AND
((`last_contact` >= `last_failure`) OR (`updated` >= `last_failure`)) AND
(`url` LIKE '%s' OR `name` LIKE '%s' OR `location` LIKE '%s' OR
`addr` LIKE '%s' OR `about` LIKE '%s' OR `keywords` LIKE '%s') $extra_sql
GROUP BY `nurl`
ORDER BY `updated` DESC LIMIT %d, %d",
DBA::escape(Protocol::ACTIVITYPUB), DBA::escape(Protocol::DFRN), DBA::escape($ostatus), DBA::escape($diaspora),
DBA::escape(Strings::escapeHtml($search2)), DBA::escape(Strings::escapeHtml($search2)), DBA::escape(Strings::escapeHtml($search2)),
DBA::escape(Strings::escapeHtml($search2)), DBA::escape(Strings::escapeHtml($search2)), DBA::escape(Strings::escapeHtml($search2)),
$pager->getStart(), $pager->getItemsPerPage());
$j = new stdClass();
$j->total = $count[0]["total"];
$j->items_page = $pager->getItemsPerPage();
$j->page = $pager->getPage();
foreach ($results AS $result) {
if (PortableContact::alternateOStatusUrl($result["nurl"])) {
continue;
}
$urlparts = parse_url($result["nurl"]);
// Ignore results that look strange.
// For historic reasons the gcontact table does contain some garbage.
if (!empty($urlparts['query']) || !empty($urlparts['fragment'])) {
continue;
}
$result = Model\Contact::getDetailsByURL($result["nurl"], local_user());
if ($result["name"] == "") {
$result["name"] = end(explode("/", $urlparts["path"]));
}
$objresult = new stdClass();
$objresult->cid = $result["cid"];
$objresult->pcid = $result["zid"];
$objresult->name = $result["name"];
$objresult->addr = $result["addr"];
$objresult->url = $result["url"];
$objresult->photo = $result["photo"];
$objresult->tags = $result["keywords"];
$objresult->network = $result["network"];
$j->results[] = $objresult;
}
// Add found profiles from the global directory to the local directory
Worker::add(PRIORITY_LOW, 'DiscoverPoCo', "dirsearch", urlencode($search));
} elseif (strlen(Config::get('system','directory'))) {
$p = (($pager->getPage() != 1) ? '&p=' . $pager->getPage() : '');
$x = Network::fetchUrl(get_server() . '/lsearch?f=' . $p . '&search=' . urlencode($search));
$j = json_decode($x);
$pager->setItemsPerPage($j->items_page);
}
if (!empty($j->results)) {
$id = 0;
$entries = [];
foreach ($j->results as $jj) {
$alt_text = "";
$contact_details = Model\Contact::getDetailsByURL($jj->url, local_user());
$itemurl = (($contact_details["addr"] != "") ? $contact_details["addr"] : $jj->url);
// If We already know this contact then don't show the "connect" button
if ($jj->cid > 0) {
$connlnk = "";
$conntxt = "";
$contact = DBA::selectFirst('contact', [], ['id' => $jj->cid]);
if (DBA::isResult($contact)) {
$photo_menu = Model\Contact::photoMenu($contact);
$details = Module\Contact::getContactTemplateVars($contact);
$alt_text = $details['alt_text'];
} else {
$photo_menu = [];
}
} else {
$connlnk = System::baseUrl().'/follow/?url='.(!empty($jj->connect) ? $jj->connect : $jj->url);
$conntxt = L10n::t('Connect');
$contact = DBA::selectFirst('contact', [], ['id' => $jj->pcid]);
if (DBA::isResult($contact)) {
$photo_menu = Model\Contact::photoMenu($contact);
} else {
$photo_menu = [];
}
$photo_menu['profile'] = [L10n::t("View Profile"), Model\Contact::magicLink($jj->url)];
$photo_menu['follow'] = [L10n::t("Connect/Follow"), $connlnk];
}
$jj->photo = str_replace("http:///photo/", get_server()."/photo/", $jj->photo);
$entry = [
'alt_text' => $alt_text,
'url' => Model\Contact::magicLink($jj->url),
'itemurl' => $itemurl,
'name' => $jj->name,
'thumb' => ProxyUtils::proxifyUrl($jj->photo, false, ProxyUtils::SIZE_THUMB),
'img_hover' => $jj->tags,
'conntxt' => $conntxt,
'connlnk' => $connlnk,
'photo_menu' => $photo_menu,
'details' => $contact_details['location'],
'tags' => $contact_details['keywords'],
'about' => $contact_details['about'],
'account_type' => Model\Contact::getAccountType($contact_details),
'network' => ContactSelector::networkToName($jj->network, $jj->url),
'id' => ++$id,
];
$entries[] = $entry;
}
$tpl = Renderer::getMarkupTemplate('viewcontact_template.tpl');
$o .= Renderer::replaceMacros($tpl,[
'title' => $header,
'$contacts' => $entries,
'$paginate' => $pager->renderFull($j->total),
]);
} else {
info(L10n::t('No matches') . EOL);
}
}
return $o;
}

View file

@ -84,6 +84,10 @@ function display_init(App $a)
displayShowFeed($item['id'], $a->argc > 3 && $a->argv[3] == 'conversation.atom');
}
if ($a->argc >= 3 && $nick == 'feed-item') {
displayShowFeed($item['id'], $a->argc > 3 && $a->argv[3] == 'conversation.atom');
}
if (!empty($_SERVER['HTTP_ACCEPT']) && strstr($_SERVER['HTTP_ACCEPT'], 'application/atom+xml')) {
Logger::log('Directly serving XML for id '.$item["id"], Logger::DEBUG);
displayShowFeed($item["id"], false);
@ -186,16 +190,7 @@ function display_fetchauthor($a, $item)
$profiledata["photo"] = System::removedBaseUrl($profiledata["photo"]);
if (local_user()) {
if (in_array($profiledata["network"], [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
$profiledata["remoteconnect"] = System::baseUrl()."/follow?url=".urlencode($profiledata["url"]);
}
} elseif ($profiledata["network"] == Protocol::DFRN) {
$connect = str_replace("/profile/", "/dfrn_request/", $profiledata["url"]);
$profiledata["remoteconnect"] = $connect;
}
return($profiledata);
return $profiledata;
}
function display_content(App $a, $update = false, $update_uid = 0)

View file

@ -21,6 +21,7 @@ use Friendica\Module\Login;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Strings;
use Friendica\Util\Temporal;
use Friendica\Worker\Delivery;
function events_init(App $a)
{
@ -195,7 +196,7 @@ function events_post(App $a)
$item_id = Event::store($datarray);
if (!$cid) {
Worker::add(PRIORITY_HIGH, "Notifier", "event", $item_id);
Worker::add(PRIORITY_HIGH, "Notifier", Delivery::POST, $item_id);
}
$a->internalRedirect('events');
@ -246,7 +247,7 @@ function events_content(App $a)
$tabs = '';
// tabs
if ($a->theme_events_in_profile) {
$tabs = Profile::getTabs($a, true);
$tabs = Profile::getTabs($a, 'events', true);
}
$mode = 'view';

View file

@ -10,6 +10,7 @@ use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Strings;
use Friendica\Worker\Delivery;
function fsuggest_post(App $a)
{
@ -51,7 +52,7 @@ function fsuggest_post(App $a)
'photo' => $contact['avatar'], 'note' => $note, 'created' => DateTimeFormat::utcNow()];
DBA::insert('fsuggest', $fields);
Worker::add(PRIORITY_HIGH, 'Notifier', 'suggest', DBA::lastInsertId());
Worker::add(PRIORITY_HIGH, 'Notifier', Delivery::SUGGESTION, DBA::lastInsertId());
info(L10n::t('Friend suggestion sent.') . EOL);
}

View file

@ -27,12 +27,12 @@ use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\Model\Attach;
use Friendica\Model\Contact;
use Friendica\Model\Conversation;
use Friendica\Model\FileTag;
use Friendica\Model\Item;
use Friendica\Model\Photo;
use Friendica\Model\Attach;
use Friendica\Model\Term;
use Friendica\Protocol\Diaspora;
use Friendica\Protocol\Email;
@ -40,6 +40,7 @@ use Friendica\Util\DateTimeFormat;
use Friendica\Util\Emailer;
use Friendica\Util\Security;
use Friendica\Util\Strings;
use Friendica\Worker\Delivery;
require_once 'include/items.php';
@ -327,10 +328,9 @@ function item_post(App $a) {
}
}
if (!empty($categories))
{
if (!empty($categories)) {
// get the "fileas" tags for this post
$filedas = FileTag::fileToList($categories, 'file');
$filedas = FileTag::fileToArray($categories);
}
// save old and new categories, so we can determine what needs to be deleted from pconfig
@ -338,10 +338,9 @@ function item_post(App $a) {
$categories = FileTag::listToFile(trim(defaults($_REQUEST, 'category', '')), 'category');
$categories_new = $categories;
if (!empty($filedas))
{
if (!empty($filedas) && is_array($filedas)) {
// append the fileas stuff to the new categories list
$categories .= FileTag::listToFile($filedas, 'file');
$categories .= FileTag::arrayToFile($filedas);
}
// get contact info for poster
@ -605,8 +604,6 @@ function item_post(App $a) {
$origin = $_REQUEST['origin'];
}
$notify_type = ($toplevel_item_id ? 'comment-new' : 'wall-new');
$uri = ($message_id ? $message_id : Item::newURI($api_source ? $profile_uid : $uid, $guid));
// Fallback so that we alway have a parent uri
@ -871,7 +868,7 @@ function item_post(App $a) {
// When we are doing some forum posting via ! we have to start the notifier manually.
// These kind of posts don't initiate the notifier call in the item class.
if ($only_to_forum) {
Worker::add(PRIORITY_HIGH, "Notifier", $notify_type, $post_id);
Worker::add(PRIORITY_HIGH, "Notifier", Delivery::POST, $post_id);
}
Logger::log('post_complete');

View file

@ -78,9 +78,7 @@ function network_init(App $a)
// convert query string to array. remove friendica args
$query_array = [];
$query_string = str_replace($a->cmd . '?', '', $a->query_string);
parse_str($query_string, $query_array);
array_shift($query_array);
parse_str(parse_url($a->query_string, PHP_URL_QUERY), $query_array);
// fetch last used network view and redirect if needed
if (!$is_a_date_query) {
@ -100,7 +98,7 @@ function network_init(App $a)
if ($remember_tab) {
// redirect if current selected tab is '/network' and
// last selected tab is _not_ '/network?f=&order=comment'.
// last selected tab is _not_ '/network?order=comment'.
// and this isn't a date query
$tab_baseurls = [
@ -112,12 +110,12 @@ function network_init(App $a)
'', //bookmarked
];
$tab_args = [
'f=&order=comment', //all
'f=&order=post', //postord
'f=&conv=1', //conv
'order=comment', //all
'order=post', //postord
'conv=1', //conv
'', //new
'f=&star=1', //starred
'f=&bmark=1', //bookmarked
'star=1', //starred
'bmark=1', //bookmarked
];
$k = array_search('active', $last_sel_tabs);
@ -141,7 +139,7 @@ function network_init(App $a)
if ($remember_tab) {
$net_args = array_merge($query_array, $net_args);
$net_queries = build_querystring($net_args);
$net_queries = http_build_query($net_args);
$redir_url = ($net_queries ? $net_baseurl . '?' . $net_queries : $net_baseurl);
@ -155,7 +153,7 @@ function network_init(App $a)
$a->page['aside'] .= Group::sidebarWidget('network/0', 'network', 'standard', $group_id);
$a->page['aside'] .= ForumManager::widget(local_user(), $cid);
$a->page['aside'] .= posted_date_widget('network', local_user(), false);
$a->page['aside'] .= Widget::postedByYear('network', local_user(), false);
$a->page['aside'] .= Widget::networks('network', defaults($_GET, 'nets', '') );
$a->page['aside'] .= saved_searches($search);
$a->page['aside'] .= Widget::fileAs('network', defaults($_GET, 'file', '') );
@ -203,12 +201,12 @@ function saved_searches($search)
*
* urls -> returns
* '/network' => $no_active = 'active'
* '/network?f=&order=comment' => $comment_active = 'active'
* '/network?f=&order=post' => $postord_active = 'active'
* '/network?f=&conv=1', => $conv_active = 'active'
* '/network?order=comment' => $comment_active = 'active'
* '/network?order=post' => $postord_active = 'active'
* '/network?conv=1', => $conv_active = 'active'
* '/network/new', => $new_active = 'active'
* '/network?f=&star=1', => $starred_active = 'active'
* '/network?f=&bmark=1', => $bookmarked_active = 'active'
* '/network?star=1', => $starred_active = 'active'
* '/network?bmark=1', => $bookmarked_active = 'active'
*
* @param App $a
* @return array ($no_active, $comment_active, $postord_active, $conv_active, $new_active, $starred_active, $bookmarked_active);
@ -974,7 +972,7 @@ function network_tabs(App $a)
$tabs = [
[
'label' => L10n::t('Commented Order'),
'url' => str_replace('/new', '', $cmd) . '?f=&order=comment' . (!empty($_GET['cid']) ? '&cid=' . $_GET['cid'] : ''),
'url' => str_replace('/new', '', $cmd) . '?order=comment' . (!empty($_GET['cid']) ? '&cid=' . $_GET['cid'] : ''),
'sel' => $all_active,
'title' => L10n::t('Sort by Comment Date'),
'id' => 'commented-order-tab',
@ -982,7 +980,7 @@ function network_tabs(App $a)
],
[
'label' => L10n::t('Posted Order'),
'url' => str_replace('/new', '', $cmd) . '?f=&order=post' . (!empty($_GET['cid']) ? '&cid=' . $_GET['cid'] : ''),
'url' => str_replace('/new', '', $cmd) . '?order=post' . (!empty($_GET['cid']) ? '&cid=' . $_GET['cid'] : ''),
'sel' => $postord_active,
'title' => L10n::t('Sort by Post Date'),
'id' => 'posted-order-tab',
@ -992,7 +990,7 @@ function network_tabs(App $a)
$tabs[] = [
'label' => L10n::t('Personal'),
'url' => str_replace('/new', '', $cmd) . (!empty($_GET['cid']) ? '/?f=&cid=' . $_GET['cid'] : '/?f=') . '&conv=1',
'url' => str_replace('/new', '', $cmd) . (!empty($_GET['cid']) ? '/?cid=' . $_GET['cid'] : '/?f=') . '&conv=1',
'sel' => $conv_active,
'title' => L10n::t('Posts that mention or involve you'),
'id' => 'personal-tab',
@ -1002,7 +1000,7 @@ function network_tabs(App $a)
if (Feature::isEnabled(local_user(), 'new_tab')) {
$tabs[] = [
'label' => L10n::t('New'),
'url' => 'network/new' . (!empty($_GET['cid']) ? '/?f=&cid=' . $_GET['cid'] : ''),
'url' => 'network/new' . (!empty($_GET['cid']) ? '/?cid=' . $_GET['cid'] : ''),
'sel' => $new_active,
'title' => L10n::t('Activity Stream - by date'),
'id' => 'activitiy-by-date-tab',
@ -1013,7 +1011,7 @@ function network_tabs(App $a)
if (Feature::isEnabled(local_user(), 'link_tab')) {
$tabs[] = [
'label' => L10n::t('Shared Links'),
'url' => str_replace('/new', '', $cmd) . (!empty($_GET['cid']) ? '/?f=&cid=' . $_GET['cid'] : '/?f=') . '&bmark=1',
'url' => str_replace('/new', '', $cmd) . (!empty($_GET['cid']) ? '/?cid=' . $_GET['cid'] : '/?f=') . '&bmark=1',
'sel' => $bookmarked_active,
'title' => L10n::t('Interesting Links'),
'id' => 'shared-links-tab',
@ -1023,7 +1021,7 @@ function network_tabs(App $a)
$tabs[] = [
'label' => L10n::t('Starred'),
'url' => str_replace('/new', '', $cmd) . (!empty($_GET['cid']) ? '/?f=&cid=' . $_GET['cid'] : '/?f=') . '&star=1',
'url' => str_replace('/new', '', $cmd) . (!empty($_GET['cid']) ? '/?cid=' . $_GET['cid'] : '/?f=') . '&star=1',
'sel' => $starred_active,
'title' => L10n::t('Favourite Posts'),
'id' => 'starred-posts-tab',

View file

@ -1,61 +0,0 @@
<?php
/**
* @file mod/newmember.php
*/
use Friendica\App;
use Friendica\Core\Config;
use Friendica\Core\L10n;
function newmember_content(App $a)
{
$o = '<div class="generic-page-wrapper">';
$o .= '<h1>' . L10n::t('Welcome to Friendica') . '</h1>';
$o .= '<h3>' . L10n::t('New Member Checklist') . '</h3>';
$o .= '<div style="font-size: 120%;">';
$o .= L10n::t('We would like to offer some tips and links to help make your experience enjoyable. Click any item to visit the relevant page. A link to this page will be visible from your home page for two weeks after your initial registration and then will quietly disappear.');
$o .= '<h4>' . L10n::t('Getting Started') . '</h4>';
$o .= '<ul>';
$o .= '<li> ' . '<a target="newmember" href="help/Quick-Start-guide">' . L10n::t('Friendica Walk-Through') . '</a><br />' . L10n::t('On your <em>Quick Start</em> page - find a brief introduction to your profile and network tabs, make some new connections, and find some groups to join.') . '</li>' . EOL;
$o .= '</ul>';
$o .= '<h4>' . L10n::t('Settings') . '</h4>';
$o .= '<ul>';
$o .= '<li>' . '<a target="newmember" href="settings">' . L10n::t('Go to Your Settings') . '</a><br />' . L10n::t('On your <em>Settings</em> page - change your initial password. Also make a note of your Identity Address. This looks just like an email address - and will be useful in making friends on the free social web.') . '</li>' . EOL;
$o .= '<li>' . L10n::t('Review the other settings, particularly the privacy settings. An unpublished directory listing is like having an unlisted phone number. In general, you should probably publish your listing - unless all of your friends and potential friends know exactly how to find you.') . '</li>' . EOL;
$o .= '</ul>';
$o .= '<h4>' . L10n::t('Profile') . '</h4>';
$o .= '<ul>';
$o .= '<li>' . '<a target="newmember" href="profile_photo">' . L10n::t('Upload Profile Photo') . '</a><br />' . L10n::t('Upload a profile photo if you have not done so already. Studies have shown that people with real photos of themselves are ten times more likely to make friends than people who do not.') . '</li>' . EOL;
$o .= '<li>' . '<a target="newmember" href="profiles">' . L10n::t('Edit Your Profile') . '</a><br />' . L10n::t('Edit your <strong>default</strong> profile to your liking. Review the settings for hiding your list of friends and hiding the profile from unknown visitors.') . '</li>' . EOL;
$o .= '<li>' . '<a target="newmember" href="profiles">' . L10n::t('Profile Keywords') . '</a><br />' . L10n::t('Set some public keywords for your default profile which describe your interests. We may be able to find other people with similar interests and suggest friendships.') . '</li>' . EOL;
$o .= '</ul>';
$o .= '<h4>' . L10n::t('Connecting') . '</h4>';
$o .= '<ul>';
$mail_disabled = ((function_exists('imap_open') && (!Config::get('system', 'imap_disabled'))) ? 0 : 1);
if (!$mail_disabled) {
$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="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="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="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;
}
$o .= '</ul>';
$o .= '<h4>' . L10n::t('Getting Help') . '</h4>';
$o .= '<ul>';
$o .= '<li>' . '<a target="newmember" href="help">' . L10n::t('Go to the Help Section') . '</a><br />' . L10n::t('Our <strong>help</strong> pages may be consulted for detail on other program features and resources.') . '</li>' . EOL;
$o .= '</ul>';
$o .= '</div>';
$o .= '</div>';
return $o;
}

View file

@ -28,7 +28,7 @@ function notes_content(App $a, $update = false)
return;
}
$o = Profile::getTabs($a, true);
$o = Profile::getTabs($a, 'notes', true);
if (!$update) {
$o .= '<h3>' . L10n::t('Personal Notes') . '</h3>';

View file

@ -121,6 +121,9 @@ 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);
// fallback - redirect to main page
} else {
$a->internalRedirect('notifications');
}
// Set the pager

View file

@ -9,12 +9,14 @@
*
* @see ParseUrl::getSiteinfo() for more information about scraping embeddable content
*/
use Friendica\App;
use Friendica\Core\Hook;
use Friendica\Core\Logger;
use Friendica\Core\System;
use Friendica\Util\Network;
use Friendica\Util\ParseUrl;
use Friendica\Util\Strings;
function parse_url_content(App $a)
{
@ -25,10 +27,14 @@ function parse_url_content(App $a)
$br = "\n";
if (!empty($_GET['binurl'])) {
if (!empty($_GET['binurl']) && Strings::isHex($_GET['binurl'])) {
$url = trim(hex2bin($_GET['binurl']));
} else {
} elseif (!empty($_GET['url'])) {
$url = trim($_GET['url']);
// fallback in case no url is valid
} else {
Logger::info('No url given');
exit();
}
if (!empty($_GET['title'])) {
@ -64,9 +70,8 @@ function parse_url_content(App $a)
// 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
$curlResponse = Network::curl($url, false, $redirects, ['novalidate' => true, 'nobody' => true]);
$curlResponse = Network::curl($url, false, ['novalidate' => true, 'nobody' => true]);
if ($curlResponse->isSuccess()) {
// Convert the header fields into an array

View file

@ -29,8 +29,8 @@ use Friendica\Util\Crypto;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Map;
use Friendica\Util\Security;
use Friendica\Util\Temporal;
use Friendica\Util\Strings;
use Friendica\Util\Temporal;
use Friendica\Util\XML;
function photos_init(App $a) {
@ -188,6 +188,9 @@ function photos_post(App $a)
}
if ($a->argc > 3 && $a->argv[2] === 'album') {
if (!Strings::isHex($a->argv[3])) {
$a->internalRedirect('photos/' . $a->data['user']['nickname'] . '/album');
}
$album = hex2bin($a->argv[3]);
if ($album === L10n::t('Profile Photos') || $album === 'Contact Photos' || $album === L10n::t('Contact Photos')) {
@ -315,7 +318,7 @@ function photos_post(App $a)
$str_group_deny = !empty($_POST['group_deny']) ? perms2str($_POST['group_deny']) : '';
$str_contact_deny = !empty($_POST['contact_deny']) ? perms2str($_POST['contact_deny']) : '';
$resource_id = $a->argv[2];
$resource_id = $a->argv[3];
if (!strlen($albname)) {
$albname = DateTimeFormat::localNow('Y');
@ -418,10 +421,11 @@ function photos_post(App $a)
if ($item_id) {
$item = Item::selectFirst(['tag', 'inform'], ['id' => $item_id, 'uid' => $page_owner_uid]);
}
if (DBA::isResult($item)) {
$old_tag = $item['tag'];
$old_inform = $item['inform'];
if (DBA::isResult($item)) {
$old_tag = $item['tag'];
$old_inform = $item['inform'];
}
}
if (strlen($rawtags)) {
@ -524,13 +528,13 @@ function photos_post(App $a)
}
}
$newtag = $old_tag;
$newtag = $old_tag ?? '';
if (strlen($newtag) && strlen($str_tags)) {
$newtag .= ',';
}
$newtag .= $str_tags;
$newinform = $old_inform;
$newinform = $old_inform ?? '';
if (strlen($newinform) && strlen($inform)) {
$newinform .= ',';
}
@ -735,7 +739,7 @@ function photos_post(App $a)
@unlink($src);
$foo = 0;
Hook::callAll('photo_post_end',$foo);
exit();
return;
}
$exif = $image->orient($src);
@ -761,7 +765,7 @@ function photos_post(App $a)
if (!$r) {
Logger::log('mod/photos.php: photos_post(): image store failed', Logger::DEBUG);
notice(L10n::t('Image upload failed.') . EOL);
exit();
return;
}
if ($width > 640 || $height > 640) {
@ -950,7 +954,7 @@ function photos_content(App $a)
// tabs
$is_owner = (local_user() && (local_user() == $owner_uid));
$o .= Profile::getTabs($a, $is_owner, $a->data['user']['nickname']);
$o .= Profile::getTabs($a, 'photos', $is_owner, $a->data['user']['nickname']);
// Display upload form
if ($datatype === 'upload') {
@ -959,7 +963,7 @@ function photos_content(App $a)
return;
}
$selname = $datum ? hex2bin($datum) : '';
$selname = Strings::isHex($datum) ? hex2bin($datum) : '';
$albumselect = '';
@ -1026,6 +1030,10 @@ function photos_content(App $a)
// Display a single photo album
if ($datatype === 'album') {
// if $datum is not a valid hex, redirect to the default page
if (!Strings::isHex($datum)) {
$a->internalRedirect('photos/' . $a->data['user']['nickname']. '/album');
}
$album = hex2bin($datum);
$total = 0;
@ -1503,7 +1511,7 @@ function photos_content(App $a)
'$title' => $title_e,
'$body' => $body_e,
'$ago' => Temporal::getRelativeDate($item['created']),
'$indent' => (($item['parent'] != $item['item_id']) ? ' comment' : ''),
'$indent' => (($item['parent'] != $item['id']) ? ' comment' : ''),
'$drop' => $drop,
'$comment' => $comment
]);
@ -1512,7 +1520,7 @@ function photos_content(App $a)
$comments .= Renderer::replaceMacros($cmnt_tpl, [
'$return_path' => '',
'$jsreload' => $return_path,
'$id' => $item['item_id'],
'$id' => $item['id'],
'$parent' => $item['parent'],
'$profile_uid' => $owner_uid,
'$mylink' => $contact['url'],

View file

@ -3,12 +3,13 @@
use Friendica\App;
use Friendica\Core\L10n;
use Friendica\Core\Logger;
use Friendica\Core\Session;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Model\Profile;
use Friendica\Util\Strings;
use Friendica\Util\Network;
use Friendica\Util\Strings;
function redir_init(App $a) {
@ -70,7 +71,9 @@ function redir_init(App $a) {
&& is_array($_SESSION['remote']))
{
foreach ($_SESSION['remote'] as $v) {
if ($v['uid'] == $_SESSION['visitor_visiting'] && $v['cid'] == $_SESSION['visitor_id']) {
if (!empty($v['uid']) && !empty($v['cid']) &&
$v['uid'] == Session::get('visitor_visiting') &&
$v['cid'] == Session::get('visitor_id')) {
// Remote user is already authenticated.
$target_url = defaults($url, $contact_url);
Logger::log($contact['name'] . " is already authenticated. Redirecting to " . $target_url, Logger::DEBUG);

View file

@ -12,13 +12,11 @@ use Friendica\Core\Config;
use Friendica\Core\L10n;
use Friendica\Core\Logger;
use Friendica\Core\Renderer;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Model\Item;
use Friendica\Module\BaseSearchModule;
use Friendica\Util\Strings;
require_once 'mod/dirfind.php';
function search_saved_searches() {
$o = '';
@ -150,10 +148,10 @@ function search_content(App $a) {
$search = substr($search,1);
}
if (strpos($search,'@') === 0) {
return dirfind_content($a);
return BaseSearchModule::performSearch();
}
if (strpos($search,'!') === 0) {
return dirfind_content($a);
return BaseSearchModule::performSearch();
}
if (!empty($_GET['search-option']))
@ -164,11 +162,9 @@ function search_content(App $a) {
$tag = true;
break;
case 'contacts':
return dirfind_content($a, "@");
break;
return BaseSearchModule::performSearch('@');
case 'forums':
return dirfind_content($a, "!");
break;
return BaseSearchModule::performSearch('!');
}
if (!$search)

View file

@ -28,6 +28,7 @@ use Friendica\Protocol\Email;
use Friendica\Util\Network;
use Friendica\Util\Strings;
use Friendica\Util\Temporal;
use Friendica\Worker\Delivery;
function get_theme_config_file($theme)
{
@ -390,7 +391,7 @@ function settings_post(App $a)
BaseModule::checkFormSecurityTokenRedirectOnError('/settings', 'settings');
if (!empty($_POST['resend_relocate'])) {
Worker::add(PRIORITY_HIGH, 'Notifier', 'relocate', local_user());
Worker::add(PRIORITY_HIGH, 'Notifier', Delivery::RELOCATION, local_user());
info(L10n::t("Relocate message has been send to your contacts"));
$a->internalRedirect('settings');
}

View file

@ -12,6 +12,7 @@ use Friendica\Database\DBA;
use Friendica\Model\Item;
use Friendica\Util\Strings;
use Friendica\Util\XML;
use Friendica\Worker\Delivery;
function tagger_content(App $a) {
@ -194,7 +195,7 @@ EOT;
Hook::callAll('post_local_end', $arr);
Worker::add(PRIORITY_HIGH, "Notifier", "tag", $post_id);
Worker::add(PRIORITY_HIGH, "Notifier", Delivery::POST, $post_id);
exit();
}

View file

@ -2,20 +2,27 @@
/**
* @file mod/uexport.php
*/
use Friendica\App;
use Friendica\Core\Hook;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
function uexport_init(App $a) {
/// @todo Don't forget to move this global field as static field in src/Modules
global $dbStructure;
if (!local_user()) {
exit();
}
require_once("mod/settings.php");
settings_init($a);
$dbStructure = DBStructure::definition($a->getBasePath());
}
function uexport_content(App $a) {
@ -55,13 +62,25 @@ function uexport_content(App $a) {
}
function _uexport_multirow($query) {
global $dbStructure;
preg_match("/\s+from\s+`?([a-z\d_]+)`?/i", $query, $match);
$table = $match[1];
$result = [];
$r = q($query);
if (DBA::isResult($r)) {
foreach ($r as $rr) {
$p = [];
foreach ($rr as $k => $v) {
$p[$k] = $v;
switch ($dbStructure[$table]['fields'][$k]['type']) {
case 'datetime':
$p[$k] = $v ?? DBA::NULL_DATETIME;
break;
default:
$p[$k] = $v;
break;
}
}
$result[] = $p;
}
@ -70,12 +89,25 @@ function _uexport_multirow($query) {
}
function _uexport_row($query) {
global $dbStructure;
preg_match("/\s+from\s+`?([a-z\d_]+)`?/i", $query, $match);
$table = $match[1];
$result = [];
$r = q($query);
if (DBA::isResult($r)) {
foreach ($r as $rr) {
foreach ($rr as $k => $v) {
$result[$k] = $v;
switch ($dbStructure[$table]['fields'][$k]['type']) {
case 'datetime':
$result[$k] = $v ?? DBA::NULL_DATETIME;
break;
default:
$result[$k] = $v;
break;
}
}
}
}

View file

@ -217,7 +217,7 @@ function videos_content(App $a)
// tabs
$_is_owner = (local_user() && (local_user() == $owner_uid));
$o .= Profile::getTabs($a, $_is_owner, $a->data['user']['nickname']);
$o .= Profile::getTabs($a, 'videos', $_is_owner, $a->data['user']['nickname']);
//
// dispatch request

View file

@ -1,120 +0,0 @@
<?php
/**
* @file mod/viewcontacts.php
*/
use Friendica\App;
use Friendica\Content\ContactSelector;
use Friendica\Content\Nav;
use Friendica\Content\Pager;
use Friendica\Core\Config;
use Friendica\Core\L10n;
use Friendica\Core\Protocol;
use Friendica\Core\Renderer;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Model\Profile;
use Friendica\Util\Proxy as ProxyUtils;
function viewcontacts_init(App $a)
{
if (Config::get('system', 'block_public') && !local_user() && !remote_user()) {
throw new \Friendica\Network\HTTPException\ForbiddenException(L10n::t('Access denied.'));
}
if ($a->argc < 2) {
throw new \Friendica\Network\HTTPException\ForbiddenException(L10n::t('Access denied.'));
}
Nav::setSelected('home');
$user = DBA::selectFirst('user', [], ['nickname' => $a->argv[1], 'blocked' => false]);
if (!DBA::isResult($user)) {
throw new \Friendica\Network\HTTPException\NotFoundException();
}
$a->data['user'] = $user;
$a->profile_uid = $user['uid'];
Profile::load($a, $a->argv[1]);
}
function viewcontacts_content(App $a)
{
if (Config::get('system', 'block_public') && !local_user() && !remote_user()) {
notice(L10n::t('Public access denied.') . EOL);
return;
}
$is_owner = $a->profile['profile_uid'] == local_user();
// tabs
$o = Profile::getTabs($a, $is_owner, $a->data['user']['nickname']);
if (!count($a->profile) || $a->profile['hide-friends']) {
notice(L10n::t('Permission denied.') . EOL);
return $o;
}
$condition = [
'uid' => $a->profile['uid'],
'blocked' => false,
'pending' => false,
'hidden' => false,
'archive' => false,
'network' => [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS]
];
$total = DBA::count('contact', $condition);
$pager = new Pager($a->query_string);
$params = ['order' => ['name' => false], 'limit' => [$pager->getStart(), $pager->getItemsPerPage()]];
$contacts_stmt = DBA::select('contact', [], $condition, $params);
if (!DBA::isResult($contacts_stmt)) {
info(L10n::t('No contacts.') . EOL);
return $o;
}
$contacts = [];
while ($contact = DBA::fetch($contacts_stmt)) {
/// @TODO This triggers an E_NOTICE if 'self' is not there
if ($contact['self']) {
continue;
}
$contact_details = Contact::getDetailsByURL($contact['url'], $a->profile['uid'], $contact);
$contacts[] = [
'id' => $contact['id'],
'img_hover' => L10n::t('Visit %s\'s profile [%s]', $contact_details['name'], $contact['url']),
'photo_menu' => Contact::photoMenu($contact),
'thumb' => ProxyUtils::proxifyUrl($contact_details['thumb'], false, ProxyUtils::SIZE_THUMB),
'name' => substr($contact_details['name'], 0, 20),
'username' => $contact_details['name'],
'details' => $contact_details['location'],
'tags' => $contact_details['keywords'],
'about' => $contact_details['about'],
'account_type' => Contact::getAccountType($contact_details),
'url' => Contact::magicLink($contact['url']),
'sparkle' => '',
'itemurl' => (($contact_details['addr'] != "") ? $contact_details['addr'] : $contact['url']),
'network' => ContactSelector::networkToName($contact['network'], $contact['url']),
];
}
DBA::close($contacts_stmt);
$tpl = Renderer::getMarkupTemplate("viewcontact_template.tpl");
$o .= Renderer::replaceMacros($tpl, [
'$title' => L10n::t('Contacts'),
'$contacts' => $contacts,
'$paginate' => $pager->renderFull($total),
]);
return $o;
}

View file

@ -993,12 +993,6 @@ class App
);
}
if (strstr($this->query_string, '.well-known/host-meta') && ($this->query_string != '.well-known/host-meta')) {
Module\Special\HTTPException::rawContent(
new HTTPException\NotFoundException()
);
}
if (!$this->getMode()->isInstall()) {
// Force SSL redirection
if ($this->baseURL->checkRedirectHttps()) {
@ -1105,7 +1099,7 @@ class App
// Compatibility with the Android Diaspora client
if ($this->module == 'stream') {
$this->internalRedirect('network?f=&order=post');
$this->internalRedirect('network?order=post');
}
if ($this->module == 'conversations') {
@ -1113,15 +1107,15 @@ class App
}
if ($this->module == 'commented') {
$this->internalRedirect('network?f=&order=comment');
$this->internalRedirect('network?order=comment');
}
if ($this->module == 'liked') {
$this->internalRedirect('network?f=&order=comment');
$this->internalRedirect('network?order=comment');
}
if ($this->module == 'activity') {
$this->internalRedirect('network/?f=&conv=1');
$this->internalRedirect('network?conv=1');
}
if (($this->module == 'status_messages') && ($this->cmd == 'status_messages/new')) {

View file

@ -112,6 +112,7 @@ class Router
$collector->addRoute(['GET'], '/ignored', Module\Contact::class);
});
$this->routeCollector->addRoute(['GET'], '/credits', Module\Credits::class);
$this->routeCollector->addRoute(['GET'], '/dirfind', Module\Search\Directory::class);
$this->routeCollector->addRoute(['GET'], '/directory', Module\Directory::class);
$this->routeCollector->addGroup('/feed', function (RouteCollector $collector) {
$collector->addRoute(['GET'], '/{nickname}', Module\Feed::class);
@ -122,9 +123,9 @@ class Router
});
$this->routeCollector->addRoute(['GET'], '/feedtest', Module\Debug\Feed::class);
$this->routeCollector->addGroup('/fetch', function (RouteCollector $collector) {
$collector->addRoute(['GET'], '/{guid}/post', Module\Diaspora\Fetch::class);
$collector->addRoute(['GET'], '/{guid}/status_message', Module\Diaspora\Fetch::class);
$collector->addRoute(['GET'], '/{guid}/reshare', Module\Diaspora\Fetch::class);
$collector->addRoute(['GET'], '/post/{guid}', Module\Diaspora\Fetch::class);
$collector->addRoute(['GET'], '/status_message/{guid}', Module\Diaspora\Fetch::class);
$collector->addRoute(['GET'], '/reshare/{guid}', Module\Diaspora\Fetch::class);
});
$this->routeCollector->addRoute(['GET'], '/filer[/{id:\d+}]', Module\Filer\SaveTag::class);
$this->routeCollector->addRoute(['GET'], '/filerm/{id:\d+}', Module\Filer\RemoveTag::class);
@ -160,6 +161,7 @@ class Router
$this->routeCollector->addRoute(['GET'], '/maintenance', Module\Maintenance::class);
$this->routeCollector->addRoute(['GET'], '/manifest', Module\Manifest::class);
$this->routeCollector->addRoute(['GET'], '/modexp/{nick}', Module\PublicRSAKey::class);
$this->routeCollector->addRoute(['GET'], '/newmember', Module\Welcome::class);
$this->routeCollector->addRoute(['GET'], '/nodeinfo/1.0', Module\NodeInfo::class);
$this->routeCollector->addRoute(['GET'], '/nogroup', Module\Group::class);
$this->routeCollector->addGroup('/notify', function (RouteCollector $collector) {
@ -167,7 +169,6 @@ class Router
$collector->addRoute(['GET'], '/view/{id:\d+}', Module\Notifications\Notify::class);
$collector->addRoute(['GET'], '/mark/all', Module\Notifications\Notify::class);
});
$this->routeCollector->addRoute(['GET'], '/notice/{id:\d+}', Module\GnuSocial\Notice::class);
$this->routeCollector->addRoute(['GET'], '/objects/{guid}', Module\Objects::class);
$this->routeCollector->addGroup('/oembed', function (RouteCollector $collector) {
$collector->addRoute(['GET'], '/b2h', Module\Oembed::class);
@ -186,6 +187,8 @@ class Router
$this->routeCollector->addRoute(['GET'], '/probe', Module\Debug\Probe::class);
$this->routeCollector->addGroup('/profile', function (RouteCollector $collector) {
$collector->addRoute(['GET'], '/{nickname}', Module\Profile::class);
$collector->addRoute(['GET'], '/{nickname}/{to:\d{4}-\d{2}-\d{2}}/{from:\d{4}-\d{2}-\d{2}}', Module\Profile::class);
$collector->addRoute(['GET'], '/{nickname}/contacts[/{type}]', Module\Profile\Contacts::class);
$collector->addRoute(['GET'], '/{profile:\d+}/view', Module\Profile::class);
});
$this->routeCollector->addGroup('/proxy', function (RouteCollector $collector) {

View file

@ -43,7 +43,7 @@ HELP;
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
}
$php_path = BaseObject::getApp()->getConfigCache()->get('config', 'php_path', 'php');
$php_path = BaseObject::getApp()->getConfig()->get('config', 'php_path', 'php');
if ($this->getOption('v')) {
$this->out('Directory: src');
@ -57,6 +57,18 @@ HELP;
}
}
if ($this->getOption('v')) {
$this->out('Directory: tests');
}
$Iterator = new \RecursiveDirectoryIterator('tests');
foreach (new \RecursiveIteratorIterator($Iterator) as $file) {
if (substr($file, -4) === '.php') {
$this->checkFile($php_path, $file);
}
}
if ($this->getOption('v')) {
$this->out('Directory: mod');
}

View file

@ -111,7 +111,7 @@ class ForumManager
$selected = (($cid == $contact['id']) ? ' forum-selected' : '');
$entry = [
'url' => 'network?f=&cid=' . $contact['id'],
'url' => 'network?cid=' . $contact['id'],
'external_url' => Contact::magicLink($contact['url']),
'name' => $contact['name'],
'cid' => $contact['id'],

View file

@ -149,9 +149,13 @@ class Nav
$nav['usermenu'] = [];
$userinfo = null;
if (local_user()) {
if (local_user() || remote_user()) {
$nav['logout'] = ['logout', L10n::t('Logout'), '', L10n::t('End this session')];
} else {
$nav['login'] = ['login', L10n::t('Login'), ($a->module == 'login' ? 'selected' : ''), L10n::t('Sign in')];
}
if (local_user()) {
// user menu
$nav['usermenu'][] = ['profile/' . $a->user['nickname'], L10n::t('Status'), '', L10n::t('Your posts and conversations')];
$nav['usermenu'][] = ['profile/' . $a->user['nickname'] . '?tab=profile', L10n::t('Profile'), '', L10n::t('Your profile page')];
@ -166,8 +170,6 @@ class Nav
'icon' => (DBA::isResult($contact) ? $a->removeBaseURL($contact['micro']) : 'images/person-48.jpg'),
'name' => $a->user['username'],
];
} else {
$nav['login'] = ['login', L10n::t('Login'), ($a->module == 'login' ? 'selected' : ''), L10n::t('Sign in')];
}
// "Home" should also take you home from an authenticated remote profile connection

View file

@ -83,8 +83,7 @@ class OEmbed
if (!in_array($ext, $noexts)) {
// try oembed autodiscovery
$redirects = 0;
$html_text = Network::fetchUrl($embedurl, false, $redirects, 15, 'text/*');
$html_text = Network::fetchUrl($embedurl, false, 15, 'text/*');
if ($html_text) {
$dom = @DOMDocument::loadHTML($html_text);
if ($dom) {

View file

@ -213,7 +213,8 @@ class Smilies
return $text;
}
$text = preg_replace_callback('/<code>(.*?)<\/code>/ism', 'self::encode', $text);
$text = preg_replace_callback('/<(pre)>(.*?)<\/pre>/ism', 'self::encode', $text);
$text = preg_replace_callback('/<(code)>(.*?)<\/code>/ism', 'self::encode', $text);
if ($no_images) {
$cleaned = ['texts' => [], 'icons' => []];
@ -230,7 +231,8 @@ class Smilies
$text = preg_replace_callback('/&lt;(3+)/', 'self::pregHeart', $text);
$text = self::strOrigReplace($smilies['texts'], $smilies['icons'], $text);
$text = preg_replace_callback('/<code>(.*?)<\/code>/ism', 'self::decode', $text);
$text = preg_replace_callback('/<(code)>(.*?)<\/code>/ism', 'self::decode', $text);
$text = preg_replace_callback('/<(pre)>(.*?)<\/pre>/ism', 'self::decode', $text);
return $text;
}
@ -242,7 +244,7 @@ class Smilies
*/
private static function encode($m)
{
return '<code>' . Strings::base64UrlEncode($m[1]) . '</code>';
return '<' . $m[1] . '>' . Strings::base64UrlEncode($m[2]) . '</' . $m[1] . '>';
}
/**
@ -253,7 +255,7 @@ class Smilies
*/
private static function decode($m)
{
return '<code>' . Strings::base64UrlDecode($m[1]) . '</code>';
return '<' . $m[1] . '>' . Strings::base64UrlDecode($m[2]) . '</' . $m[1] . '>';
}

View file

@ -72,9 +72,7 @@ class BBCode extends BaseObject
$attacheddata = $data[2];
$URLSearchString = "^\[\]";
if (preg_match("/\[img\]([$URLSearchString]*)\[\/img\]/ism", $attacheddata, $matches)) {
if (preg_match("/\[img\](.*?)\[\/img\]/ism", $attacheddata, $matches)) {
$picturedata = Image::getInfoFromURL($matches[1]);
@ -87,12 +85,12 @@ class BBCode extends BaseObject
}
}
if (preg_match("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism", $attacheddata, $matches)) {
if (preg_match("/\[bookmark\=(.*?)\](.*?)\[\/bookmark\]/ism", $attacheddata, $matches)) {
$post["url"] = $matches[1];
$post["title"] = $matches[2];
}
if (!empty($post["url"]) && (in_array($post["type"], ["link", "video"]))
&& preg_match("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", $attacheddata, $matches)) {
&& preg_match("/\[url\=(.*?)\](.*?)\[\/url\]/ism", $attacheddata, $matches)) {
$post["url"] = $matches[1];
}
@ -245,14 +243,18 @@ class BBCode extends BaseObject
// Simplify image codes
$body = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $body);
$URLSearchString = "^\[\]";
$body = preg_replace("/\[img\=(.*?)\](.*?)\[\/img\]/ism", '[img]$1[/img]', $body);
$body = preg_replace("/\[img\=([$URLSearchString]*)\](.*?)\[\/img\]/ism", '[img]$1[/img]', $body);
if (preg_match_all("(\[url=([$URLSearchString]*)\]\s*\[img\]([$URLSearchString]*)\[\/img\]\s*\[\/url\])ism", $body, $pictures, PREG_SET_ORDER)) {
if (preg_match_all("(\[url=(.*?)\]\s*\[img\](.*?)\[\/img\]\s*\[\/url\])ism", $body, $pictures, PREG_SET_ORDER)) {
if ((count($pictures) == 1) && !$has_title) {
// Checking, if the link goes to a picture
$data = ParseUrl::getSiteinfoCached($pictures[0][1], true);
if (!empty($item['object-type']) && ($item['object-type'] == ACTIVITY_OBJ_IMAGE)) {
// Replace the preview picture with the real picture
$url = str_replace('-1.', '-0.', $pictures[0][2]);
$data = ['url' => $url, 'type' => 'photo'];
} else {
// Checking, if the link goes to a picture
$data = ParseUrl::getSiteinfoCached($pictures[0][1], true);
}
// Workaround:
// Sometimes photo posts to the own album are not detected at the start.
@ -271,14 +273,14 @@ class BBCode extends BaseObject
}
$post["preview"] = $pictures[0][2];
$post["text"] = str_replace($pictures[0][0], "", $body);
$post["text"] = trim(str_replace($pictures[0][0], "", $body));
} else {
$imgdata = Image::getInfoFromURL($pictures[0][1]);
if ($imgdata && substr($imgdata["mime"], 0, 6) == "image/") {
$post["type"] = "photo";
$post["image"] = $pictures[0][1];
$post["preview"] = $pictures[0][2];
$post["text"] = str_replace($pictures[0][0], "", $body);
$post["text"] = trim(str_replace($pictures[0][0], "", $body));
}
}
} elseif (count($pictures) > 0) {
@ -287,7 +289,7 @@ class BBCode extends BaseObject
$post["image"] = $pictures[0][2];
$post["text"] = $body;
}
} elseif (preg_match_all("(\[img\]([$URLSearchString]*)\[\/img\])ism", $body, $pictures, PREG_SET_ORDER)) {
} elseif (preg_match_all("(\[img\](.*?)\[\/img\])ism", $body, $pictures, PREG_SET_ORDER)) {
if ((count($pictures) == 1) && !$has_title) {
$post["type"] = "photo";
$post["image"] = $pictures[0][1];
@ -301,8 +303,8 @@ class BBCode extends BaseObject
}
// Test for the external links
preg_match_all("(\[url\]([$URLSearchString]*)\[\/url\])ism", $body, $links1, PREG_SET_ORDER);
preg_match_all("(\[url\=([$URLSearchString]*)\].*?\[\/url\])ism", $body, $links2, PREG_SET_ORDER);
preg_match_all("(\[url\](.*?)\[\/url\])ism", $body, $links1, PREG_SET_ORDER);
preg_match_all("(\[url\=(.*?)\].*?\[\/url\])ism", $body, $links2, PREG_SET_ORDER);
$links = array_merge($links1, $links2);
@ -563,7 +565,7 @@ class BBCode extends BaseObject
}
$return = '';
if ($simplehtml == 7) {
if (in_array($simplehtml, [7, 9])) {
$return = self::convertUrlForOStatus($data["url"]);
} elseif (($simplehtml != 4) && ($simplehtml != 0)) {
$return = sprintf('<a href="%s" target="_blank">%s</a><br>', $data["url"], $data["title"]);
@ -979,16 +981,9 @@ class BBCode extends BaseObject
$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
case 9: // ActivityPub
$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 = ($is_quote_share? '<br />' : '') . '<p>' . html_entity_decode('&#x2672; ', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ': </p>' . "\n";
$text .= '<p>' . $content . '</p>' . "\n";
if ($attributes['link'] != '') {
$text .= '<p>' . $attributes['link'] . '</p>';
}
break;
default:
// Transforms quoted tweets in rich attachments to avoid nested tweets
if (stripos(Strings::normaliseLink($attributes['link']), 'http://twitter.com/') === 0 && OEmbed::isAllowedURL($attributes['link'])) {
@ -1140,13 +1135,14 @@ class BBCode extends BaseObject
* Simple HTML values meaning:
* - 0: Friendica display
* - 1: Unused
* - 2: Used for Google+, Windows Phone push, Friendica API
* - 2: Used for Windows Phone push, Friendica API
* - 3: Used before converting to Markdown in bb2diaspora.php
* - 4: Used for WordPress, Libertree (before Markdown), pump.io and tumblr
* - 5: Unused
* - 6: Unused
* - 7: Used for dfrn, OStatus
* - 8: Used for WP backlink text setting
* - 9: ActivityPub
*
* @param string $text
* @param bool $try_oembed
@ -1250,6 +1246,25 @@ class BBCode extends BaseObject
$text = trim($text);
$text = str_replace("\r\n", "\n", $text);
// Remove linefeeds inside of the table elements. See issue #6799
$search = ["\n[th]", "[th]\n", " [th]", "\n[/th]", "[/th]\n", "[/th] ",
"\n[td]", "[td]\n", " [td]", "\n[/td]", "[/td]\n", "[/td] ",
"\n[tr]", "[tr]\n", " [tr]", "[tr] ", "\n[/tr]", "[/tr]\n", " [/tr]", "[/tr] ",
"[table]\n", "[table] ", " [table]", "\n[/table]", " [/table]", "[/table] "];
$replace = ["[th]", "[th]", "[th]", "[/th]", "[/th]", "[/th]",
"[td]", "[td]", "[td]", "[/td]", "[/td]", "[/td]",
"[tr]", "[tr]", "[tr]", "[tr]", "[/tr]", "[/tr]", "[/tr]", "[/tr]",
"[table]", "[table]", "[table]", "[/table]", "[/table]", "[/table]"];
do {
$oldtext = $text;
$text = str_replace($search, $replace, $text);
} while ($oldtext != $text);
// Replace these here only once
$search = ["\n[table]", "[/table]\n"];
$replace = ["[table]", "[/table]"];
$text = str_replace($search, $replace, $text);
// removing multiplicated newlines
if (Config::get("system", "remove_multiplicated_lines")) {
$search = ["\n\n\n", "\n ", " \n", "[/quote]\n\n", "\n[/quote]", "[/li]\n", "\n[li]", "\n[ul]", "[/ul]\n", "\n\n[share ", "[/attachment]\n",
@ -1262,131 +1277,11 @@ class BBCode extends BaseObject
} while ($oldtext != $text);
}
// Set up the parameters for a URL search string
$URLSearchString = "^\[\]";
// Set up the parameters for a MAIL search string
$MAILSearchString = $URLSearchString;
// Handle attached links or videos
$text = self::convertAttachment($text, $simple_html, $try_oembed);
// if the HTML is used to generate plain text, then don't do this search, but replace all URL of that kind to text
if (!$for_plaintext) {
$text = preg_replace(Strings::autoLinkRegEx(), '[url]$1[/url]', $text);
if ($simple_html == 7) {
$text = preg_replace_callback("/\[url\]([$URLSearchString]*)\[\/url\]/ism", 'self::convertUrlForOStatusCallback', $text);
$text = preg_replace_callback("/\[url\=([$URLSearchString]*)\]([$URLSearchString]*)\[\/url\]/ism", 'self::convertUrlForOStatusCallback', $text);
}
} else {
$text = preg_replace("(\[url\]([$URLSearchString]*)\[\/url\])ism", " $1 ", $text);
$text = preg_replace_callback("&\[url=([^\[\]]*)\]\[img\](.*)\[\/img\]\[\/url\]&Usi", 'self::removePictureLinksCallback', $text);
}
$text = str_replace(["\r","\n"], ['<br />', '<br />'], $text);
// Remove all hashtag addresses
if ((!$try_oembed || $simple_html) && !in_array($simple_html, [3, 7])) {
$text = preg_replace("/([#@!])\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '$1$3', $text);
} elseif ($simple_html == 3) {
// The ! is converted to @ since Diaspora only understands the @
$text = preg_replace("/([@!])\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
'@<a href="$2">$3</a>',
$text);
} elseif ($simple_html == 7) {
$text = preg_replace("/([@!])\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
'$1<span class="vcard"><a href="$2" class="url u-url mention" title="$3"><span class="fn nickname mention">$3</span></a></span>',
$text);
} elseif (!$simple_html) {
$text = preg_replace("/([@!])\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
'$1<a href="$2" class="userinfo mention" title="$3">$3</a>',
$text);
}
// Bookmarks in red - will be converted to bookmarks in friendica
$text = preg_replace("/#\^\[url\]([$URLSearchString]*)\[\/url\]/ism", '[bookmark=$1]$1[/bookmark]', $text);
$text = preg_replace("/#\^\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '[bookmark=$1]$2[/bookmark]', $text);
$text = preg_replace("/#\[url\=[$URLSearchString]*\]\^\[\/url\]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/i",
"[bookmark=$1]$2[/bookmark]", $text);
if (in_array($simple_html, [2, 6, 7, 8, 9])) {
$text = preg_replace_callback("/([^#@!])\[url\=([^\]]*)\](.*?)\[\/url\]/ism", "self::expandLinksCallback", $text);
//$Text = preg_replace("/[^#@!]\[url\=([^\]]*)\](.*?)\[\/url\]/ism", ' $2 [url]$1[/url]', $Text);
$text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", ' $2 [url]$1[/url]',$text);
}
if ($simple_html == 5) {
$text = preg_replace("/[^#@!]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '[url]$1[/url]', $text);
}
// Perform URL Search
if ($try_oembed) {
$text = preg_replace_callback("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", $try_oembed_callback, $text);
}
if ($simple_html == 5) {
$text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", '[url]$1[/url]', $text);
} else {
$text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", '[url=$1]$2[/url]', $text);
}
// Handle Diaspora posts
$text = preg_replace_callback(
"&\[url=/?posts/([^\[\]]*)\](.*)\[\/url\]&Usi",
function ($match) {
return "[url=" . System::baseUrl() . "/display/" . $match[1] . "]" . $match[2] . "[/url]";
}, $text
);
$text = preg_replace_callback(
"&\[url=/people\?q\=(.*)\](.*)\[\/url\]&Usi",
function ($match) {
return "[url=" . System::baseUrl() . "/search?search=%40" . $match[1] . "]" . $match[2] . "[/url]";
}, $text
);
// Server independent link to posts and comments
// See issue: https://github.com/diaspora/diaspora_federation/issues/75
$expression = "=diaspora://.*?/post/([0-9A-Za-z\-_@.:]{15,254}[0-9A-Za-z])=ism";
$text = preg_replace($expression, System::baseUrl()."/display/$1", $text);
/* Tag conversion
* Supports:
* - #[url=<anything>]<term>[/url]
* - [url=<anything>]#<term>[/url]
*/
$text = preg_replace_callback("/(?:#\[url\=[$URLSearchString]*\]|\[url\=[$URLSearchString]*\]#)(.*?)\[\/url\]/ism", function($matches) {
return '#<a href="'
. System::baseUrl() . '/search?tag=' . rawurlencode($matches[1])
. '" class="tag" title="' . XML::escape($matches[1]) . '">'
. XML::escape($matches[1])
. '</a>';
}, $text);
// We need no target="_blank" for local links
// convert links start with System::baseUrl() as local link without the target="_blank" attribute
$escapedBaseUrl = preg_quote(System::baseUrl(), '/');
$text = preg_replace("/\[url\](".$escapedBaseUrl."[$URLSearchString]*)\[\/url\]/ism", '<a href="$1">$1</a>', $text);
$text = preg_replace("/\[url\=(".$escapedBaseUrl."[$URLSearchString]*)\](.*?)\[\/url\]/ism", '<a href="$1">$2</a>', $text);
$text = preg_replace("/\[url\]([$URLSearchString]*)\[\/url\]/ism", '<a href="$1" target="_blank">$1</a>', $text);
$text = preg_replace("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '<a href="$1" target="_blank">$2</a>', $text);
// Red compatibility, though the link can't be authenticated on Friendica
$text = preg_replace("/\[zrl\=([$URLSearchString]*)\](.*?)\[\/zrl\]/ism", '<a href="$1" target="_blank">$2</a>', $text);
// we may need to restrict this further if it picks up too many strays
// link acct:user@host to a webfinger profile redirector
$text = preg_replace('/acct:([^@]+)@((?!\-)(?:[a-zA-Z\d\-]{0,62}[a-zA-Z\d]\.){1,126}(?!\d+)[a-zA-Z\d]{1,63})/', '<a href="' . System::baseUrl() . '/acctlink?addr=$1@$2" target="extlink">acct:$1@$2</a>', $text);
// Perform MAIL Search
$text = preg_replace("/\[mail\]([$MAILSearchString]*)\[\/mail\]/", '<a href="mailto:$1">$1</a>', $text);
$text = preg_replace("/\[mail\=([$MAILSearchString]*)\](.*?)\[\/mail\]/", '<a href="mailto:$1">$2</a>', $text);
// leave open the posibility of [map=something]
// this is replaced in Item::prepareBody() which has knowledge of the item location
if (strpos($text, '[/map]') !== false) {
$text = preg_replace_callback(
"/\[map\](.*?)\[\/map\]/ism",
@ -1396,6 +1291,7 @@ class BBCode extends BaseObject
$text
);
}
if (strpos($text, '[map=') !== false) {
$text = preg_replace_callback(
"/\[map=(.*?)\]/ism",
@ -1405,6 +1301,7 @@ class BBCode extends BaseObject
$text
);
}
if (strpos($text, '[map]') !== false) {
$text = preg_replace("/\[map\]/", '<p class="map"></p>', $text);
}
@ -1471,9 +1368,9 @@ class BBCode extends BaseObject
$endlessloop = 0;
while ((((strpos($text, "[/list]") !== false) && (strpos($text, "[list") !== false)) ||
((strpos($text, "[/ol]") !== false) && (strpos($text, "[ol]") !== false)) ||
((strpos($text, "[/ul]") !== false) && (strpos($text, "[ul]") !== false)) ||
((strpos($text, "[/li]") !== false) && (strpos($text, "[li]") !== false))) && (++$endlessloop < 20)) {
((strpos($text, "[/ol]") !== false) && (strpos($text, "[ol]") !== false)) ||
((strpos($text, "[/ul]") !== false) && (strpos($text, "[ul]") !== false)) ||
((strpos($text, "[/li]") !== false) && (strpos($text, "[li]") !== false))) && (++$endlessloop < 20)) {
$text = preg_replace("/\[list\](.*?)\[\/list\]/ism", '<ul class="listbullet" style="list-style-type: circle;">$1</ul>', $text);
$text = preg_replace("/\[list=\](.*?)\[\/list\]/ism", '<ul class="listnone" style="list-style-type: none;">$1</ul>', $text);
$text = preg_replace("/\[list=1\](.*?)\[\/list\]/ism", '<ul class="listdecimal" style="list-style-type: decimal;">$1</ul>', $text);
@ -1521,8 +1418,8 @@ class BBCode extends BaseObject
$endlessloop = 0;
while ((strpos($text, "[/spoiler]")!== false) && (strpos($text, "[spoiler=") !== false) && (++$endlessloop < 20)) {
$text = preg_replace("/\[spoiler=[\"\']*(.*?)[\"\']*\](.*?)\[\/spoiler\]/ism",
"<br /><strong class=".'"spoiler"'.">" . $t_wrote . "</strong><blockquote class=".'"spoiler"'.">$2</blockquote>",
$text);
"<br /><strong class=".'"spoiler"'.">" . $t_wrote . "</strong><blockquote class=".'"spoiler"'.">$2</blockquote>",
$text);
}
// Declare the format for [quote] layout
@ -1543,8 +1440,8 @@ class BBCode extends BaseObject
$endlessloop = 0;
while ((strpos($text, "[/quote]")!== false) && (strpos($text, "[quote=") !== false) && (++$endlessloop < 20)) {
$text = preg_replace("/\[quote=[\"\']*(.*?)[\"\']*\](.*?)\[\/quote\]/ism",
"<p><strong class=".'"author"'.">" . $t_wrote . "</strong></p><blockquote>$2</blockquote>",
$text);
"<p><strong class=".'"author"'.">" . $t_wrote . "</strong></p><blockquote>$2</blockquote>",
$text);
}
@ -1565,7 +1462,7 @@ class BBCode extends BaseObject
$text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '<img src="$3" style="width: $1px;" >', $text);
$text = preg_replace("/\[zmg\=([0-9]*)x([0-9]*)\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$3" style="width: $1px;" >', $text);
$text = preg_replace_callback("/\[img\=([$URLSearchString]*)\](.*?)\[\/img\]/ism",
$text = preg_replace_callback("/\[img\=(.*?)\](.*?)\[\/img\]/ism",
function ($matches) use ($simple_html) {
$matches[1] = self::proxyUrl($matches[1], $simple_html);
$matches[2] = htmlspecialchars($matches[2], ENT_COMPAT);
@ -1591,14 +1488,6 @@ class BBCode extends BaseObject
$text = preg_replace("/\[img\](.*?)\[\/img\]/ism", '<img src="$1" alt="' . L10n::t('Image/photo') . '" />', $text);
$text = preg_replace("/\[zmg\](.*?)\[\/zmg\]/ism", '<img src="$1" alt="' . L10n::t('Image/photo') . '" />', $text);
// Shared content
$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);
//$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);
@ -1612,9 +1501,9 @@ class BBCode extends BaseObject
$text = preg_replace_callback("/\[audio\](.*?)\[\/audio\]/ism", $try_oembed_callback, $text);
} else {
$text = preg_replace("/\[video\](.*?)\[\/video\]/ism",
'<a href="$1" target="_blank">$1</a>', $text);
'<a href="$1" target="_blank">$1</a>', $text);
$text = preg_replace("/\[audio\](.*?)\[\/audio\]/ism",
'<a href="$1" target="_blank">$1</a>', $text);
'<a href="$1" target="_blank">$1</a>', $text);
}
// html5 video and audio
@ -1641,7 +1530,7 @@ class BBCode extends BaseObject
$text = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism", '<iframe width="' . $a->videowidth . '" height="' . $a->videoheight . '" src="https://www.youtube.com/embed/$1" frameborder="0" ></iframe>', $text);
} else {
$text = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism",
'<a href="https://www.youtube.com/watch?v=$1" target="_blank">https://www.youtube.com/watch?v=$1</a>', $text);
'<a href="https://www.youtube.com/watch?v=$1" target="_blank">https://www.youtube.com/watch?v=$1</a>', $text);
}
if ($try_oembed) {
@ -1656,7 +1545,7 @@ class BBCode extends BaseObject
$text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", '<iframe width="' . $a->videowidth . '" height="' . $a->videoheight . '" src="https://player.vimeo.com/video/$1" frameborder="0" ></iframe>', $text);
} else {
$text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism",
'<a href="https://vimeo.com/$1" target="_blank">https://vimeo.com/$1</a>', $text);
'<a href="https://vimeo.com/$1" target="_blank">https://vimeo.com/$1</a>', $text);
}
// oembed tag
@ -1687,6 +1576,120 @@ class BBCode extends BaseObject
$text = Smilies::replace($text);
}
// if the HTML is used to generate plain text, then don't do this search, but replace all URL of that kind to text
if (!$for_plaintext) {
$text = preg_replace(Strings::autoLinkRegEx(), '[url]$1[/url]', $text);
if (in_array($simple_html, [7, 9])) {
$text = preg_replace_callback("/\[url\](.*?)\[\/url\]/ism", 'self::convertUrlForOStatusCallback', $text);
$text = preg_replace_callback("/\[url\=(.*?)\](.*?)\[\/url\]/ism", 'self::convertUrlForOStatusCallback', $text);
}
} else {
$text = preg_replace("(\[url\](.*?)\[\/url\])ism", " $1 ", $text);
$text = preg_replace_callback("&\[url=([^\[\]]*)\]\[img\](.*)\[\/img\]\[\/url\]&Usi", 'self::removePictureLinksCallback', $text);
}
$text = str_replace(["\r","\n"], ['<br />', '<br />'], $text);
// Remove all hashtag addresses
if ((!$try_oembed || $simple_html) && !in_array($simple_html, [3, 7, 9])) {
$text = preg_replace("/([#@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', $text);
} elseif ($simple_html == 3) {
// The ! is converted to @ since Diaspora only understands the @
$text = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
'@<a href="$2">$3</a>',
$text);
} elseif (in_array($simple_html, [7, 9])) {
$text = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
'$1<span class="vcard"><a href="$2" class="url u-url mention" title="$3"><span class="fn nickname mention">$3</span></a></span>',
$text);
} elseif (!$simple_html) {
$text = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
'$1<a href="$2" class="userinfo mention" title="$3">$3</a>',
$text);
}
// Bookmarks in red - will be converted to bookmarks in friendica
$text = preg_replace("/#\^\[url\](.*?)\[\/url\]/ism", '[bookmark=$1]$1[/bookmark]', $text);
$text = preg_replace("/#\^\[url\=(.*?)\](.*?)\[\/url\]/ism", '[bookmark=$1]$2[/bookmark]', $text);
$text = preg_replace("/#\[url\=.*?\]\^\[\/url\]\[url\=(.*?)\](.*?)\[\/url\]/i",
"[bookmark=$1]$2[/bookmark]", $text);
if (in_array($simple_html, [2, 6, 7, 8])) {
$text = preg_replace_callback("/([^#@!])\[url\=([^\]]*)\](.*?)\[\/url\]/ism", "self::expandLinksCallback", $text);
//$Text = preg_replace("/[^#@!]\[url\=([^\]]*)\](.*?)\[\/url\]/ism", ' $2 [url]$1[/url]', $Text);
$text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", ' $2 [url]$1[/url]',$text);
}
if ($simple_html == 5) {
$text = preg_replace("/[^#@!]\[url\=(.*?)\](.*?)\[\/url\]/ism", '[url]$1[/url]', $text);
}
// Perform URL Search
if ($try_oembed) {
$text = preg_replace_callback("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", $try_oembed_callback, $text);
}
if ($simple_html == 5) {
$text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", '[url]$1[/url]', $text);
} else {
$text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", '[url=$1]$2[/url]', $text);
}
// Handle Diaspora posts
$text = preg_replace_callback(
"&\[url=/?posts/([^\[\]]*)\](.*)\[\/url\]&Usi",
function ($match) {
return "[url=" . System::baseUrl() . "/display/" . $match[1] . "]" . $match[2] . "[/url]";
}, $text
);
$text = preg_replace_callback(
"&\[url=/people\?q\=(.*)\](.*)\[\/url\]&Usi",
function ($match) {
return "[url=" . System::baseUrl() . "/search?search=%40" . $match[1] . "]" . $match[2] . "[/url]";
}, $text
);
// Server independent link to posts and comments
// See issue: https://github.com/diaspora/diaspora_federation/issues/75
$expression = "=diaspora://.*?/post/([0-9A-Za-z\-_@.:]{15,254}[0-9A-Za-z])=ism";
$text = preg_replace($expression, System::baseUrl()."/display/$1", $text);
/* Tag conversion
* Supports:
* - #[url=<anything>]<term>[/url]
* - [url=<anything>]#<term>[/url]
*/
$text = preg_replace_callback("/(?:#\[url\=.*?\]|\[url\=.*?\]#)(.*?)\[\/url\]/ism", function($matches) {
return '#<a href="'
. System::baseUrl() . '/search?tag=' . rawurlencode($matches[1])
. '" class="tag" title="' . XML::escape($matches[1]) . '">'
. XML::escape($matches[1])
. '</a>';
}, $text);
// We need no target="_blank" for local links
// convert links start with System::baseUrl() as local link without the target="_blank" attribute
$escapedBaseUrl = preg_quote(System::baseUrl(), '/');
$text = preg_replace("/\[url\](".$escapedBaseUrl.".*?)\[\/url\]/ism", '<a href="$1">$1</a>', $text);
$text = preg_replace("/\[url\=(".$escapedBaseUrl.".*?)\](.*?)\[\/url\]/ism", '<a href="$1">$2</a>', $text);
$text = preg_replace("/\[url\](.*?)\[\/url\]/ism", '<a href="$1" target="_blank">$1</a>', $text);
$text = preg_replace("/\[url\=(.*?)\](.*?)\[\/url\]/ism", '<a href="$1" target="_blank">$2</a>', $text);
// Red compatibility, though the link can't be authenticated on Friendica
$text = preg_replace("/\[zrl\=(.*?)\](.*?)\[\/zrl\]/ism", '<a href="$1" target="_blank">$2</a>', $text);
// we may need to restrict this further if it picks up too many strays
// link acct:user@host to a webfinger profile redirector
$text = preg_replace('/acct:([^@]+)@((?!\-)(?:[a-zA-Z\d\-]{0,62}[a-zA-Z\d]\.){1,126}(?!\d+)[a-zA-Z\d]{1,63})/', '<a href="' . System::baseUrl() . '/acctlink?addr=$1@$2" target="extlink">acct:$1@$2</a>', $text);
// Perform MAIL Search
$text = preg_replace("/\[mail\](.*?)\[\/mail\]/", '<a href="mailto:$1">$1</a>', $text);
$text = preg_replace("/\[mail\=(.*?)\](.*?)\[\/mail\]/", '<a href="mailto:$1">$2</a>', $text);
// Unhide all [noparse] contained bbtags unspacefying them
// and triming the [noparse] tag.
@ -1720,6 +1723,14 @@ class BBCode extends BaseObject
$regex = '#<([^>]*?)(href)="(?!' . implode('|', $allowed_link_protocols) . ')(.*?)"(.*?)>#ism';
$text = preg_replace($regex, '<$1$2="javascript:void(0)"$4 data-original-href="$3" class="invalid-href" title="' . L10n::t('Invalid link protocol') . '">', $text);
// Shared content
$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);
}
);
if ($saved_image) {
$text = self::interpolateSavedImagesIntoItemBody($text, $saved_image);
}

View file

@ -15,9 +15,12 @@ use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Model\FileTag;
use Friendica\Model\GContact;
use Friendica\Model\Item;
use Friendica\Model\Profile;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Proxy as ProxyUtils;
use Friendica\Util\Strings;
use Friendica\Util\Temporal;
use Friendica\Util\XML;
class Widget
@ -121,17 +124,28 @@ class Widget
}
/**
* @param string $type
* Display a generic filter widget based on a list of options
*
* The options array must be the following format:
* [
* [
* 'ref' => {filter value},
* 'name' => {option name}
* ],
* ...
* ]
*
* @param string $type The filter query string key
* @param string $title
* @param string $desc
* @param string $all
* @param string $baseUrl
* @param string $all The no filter label
* @param string $baseUrl The full page request URI
* @param array $options
* @param string $selected
* @param string $selected The currently selected filter option value
* @return string
* @throws \Exception
*/
public static function filter($type, $title, $desc, $all, $baseUrl, array $options, $selected = null)
private static function filter($type, $title, $desc, $all, $baseUrl, array $options, $selected = null)
{
$queryString = parse_url($baseUrl, PHP_URL_QUERY);
$queryArray = [];
@ -160,6 +174,37 @@ class Widget
]);
}
/**
* Return networks widget
*
* @param string $baseurl baseurl
* @param string $selected optional, default empty
* @return string
* @throws \Exception
*/
public static function contactRels($baseurl, $selected = '')
{
if (!local_user()) {
return '';
}
$options = [
['ref' => 'followers', 'name' => L10n::t('Followers')],
['ref' => 'following', 'name' => L10n::t('Following')],
['ref' => 'mutuals', 'name' => L10n::t('Mutual friends')],
];
return self::filter(
'rel',
L10n::t('Relationships'),
'',
L10n::t('All Contacts'),
$baseurl,
$options,
$selected
);
}
/**
* Return networks widget
*
@ -211,7 +256,7 @@ class Widget
* @param string $baseurl baseurl
* @param string $selected optional, default empty
* @return string|void
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \Exception
*/
public static function fileAs($baseurl, $selected = '')
{
@ -224,15 +269,9 @@ class Widget
return;
}
$matches = [];
$terms = array();
$cnt = preg_match_all('/\[(.*?)\]/', $saved, $matches, PREG_SET_ORDER);
if ($cnt) {
foreach ($matches as $mtch)
{
$unescaped = XML::escape(FileTag::decode($mtch[1]));
$terms[] = ['ref' => $unescaped, 'name' => $unescaped];
}
$terms = [];
foreach (FileTag::fileToArray($saved) as $savedFolderName) {
$terms[] = ['ref' => $savedFolderName, 'name' => $savedFolderName];
}
return self::filter(
@ -267,15 +306,9 @@ class Widget
return;
}
$matches = [];
$terms = array();
$cnt = preg_match_all('/<(.*?)>/', $saved, $matches, PREG_SET_ORDER);
if ($cnt) {
foreach ($matches as $mtch) {
$unescaped = XML::escape(FileTag::decode($mtch[1]));
$terms[] = ['ref' => $unescaped, 'name' => $unescaped];
}
foreach (FileTag::fileToArray($saved, 'category') as $savedFolderName) {
$terms[] = ['ref' => $savedFolderName, 'name' => $savedFolderName];
}
return self::filter(
@ -402,4 +435,74 @@ class Widget
return '';
}
/**
* @param string $url Base page URL
* @param int $uid User ID consulting/publishing posts
* @param bool $wall True: Posted by User; False: Posted to User (network timeline)
* @return string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function postedByYear(string $url, int $uid, bool $wall)
{
$o = '';
if (!Feature::isEnabled($uid, 'archives')) {
return $o;
}
$visible_years = PConfig::get($uid, 'system', 'archive_visible_years', 5);
/* arrange the list in years */
$dnow = DateTimeFormat::localNow('Y-m-d');
$ret = [];
$dthen = Item::firstPostDate($uid, $wall);
if ($dthen) {
// Set the start and end date to the beginning of the month
$dnow = substr($dnow, 0, 8) . '01';
$dthen = substr($dthen, 0, 8) . '01';
/*
* Starting with the current month, get the first and last days of every
* month down to and including the month of the first post
*/
while (substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
$dyear = intval(substr($dnow, 0, 4));
$dstart = substr($dnow, 0, 8) . '01';
$dend = substr($dnow, 0, 8) . Temporal::getDaysInMonth(intval($dnow), intval(substr($dnow, 5)));
$start_month = DateTimeFormat::utc($dstart, 'Y-m-d');
$end_month = DateTimeFormat::utc($dend, 'Y-m-d');
$str = L10n::getDay(DateTimeFormat::utc($dnow, 'F'));
if (empty($ret[$dyear])) {
$ret[$dyear] = [];
}
$ret[$dyear][] = [$str, $end_month, $start_month];
$dnow = DateTimeFormat::utc($dnow . ' -1 month', 'Y-m-d');
}
}
if (!DBA::isResult($ret)) {
return $o;
}
$cutoff_year = intval(DateTimeFormat::localNow('Y')) - $visible_years;
$cutoff = array_key_exists($cutoff_year, $ret);
$o = Renderer::replaceMacros(Renderer::getMarkupTemplate('widget/posted_date.tpl'),[
'$title' => L10n::t('Archives'),
'$size' => $visible_years,
'$cutoff_year' => $cutoff_year,
'$cutoff' => $cutoff,
'$url' => $url,
'$dates' => $ret,
'$showmore' => L10n::t('show more')
]);
return $o;
}
}

View file

@ -52,7 +52,7 @@ class ContactBlock
'pending' => false,
'hidden' => false,
'archive' => false,
'network' => [Protocol::DFRN, Protocol::ACTIVITYPUB, Protocol::OSTATUS, Protocol::DIASPORA],
'network' => [Protocol::DFRN, Protocol::ACTIVITYPUB, Protocol::OSTATUS, Protocol::DIASPORA, Protocol::FEED],
]);
$contacts_title = L10n::t('No contacts');

View file

@ -259,7 +259,7 @@ class ACL extends BaseObject
* @return string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function getFullSelectorHTML(array $user, $show_jotnets = false, array $default_permissions = [])
public static function getFullSelectorHTML(array $user = null, $show_jotnets = false, array $default_permissions = [])
{
// Defaults user permissions
if (empty($default_permissions)) {
@ -314,7 +314,7 @@ class ACL extends BaseObject
'$aclModalTitle' => L10n::t('Permissions'),
'$aclModalDismiss' => L10n::t('Close'),
'$features' => [
'aclautomention' => Feature::isEnabled($user['uid'], 'aclautomention') ? 'true' : 'false'
'aclautomention' => !empty($user['uid']) && Feature::isEnabled($user['uid'], 'aclautomention') ? 'true' : 'false'
],
]);

View file

@ -2,6 +2,8 @@
namespace Friendica\Core\Config\Cache;
use ParagonIE\HiddenString\HiddenString;
/**
* The Friendica config cache for the application
* Initial, all *.config.php files are loaded into this cache with the
@ -15,10 +17,17 @@ class ConfigCache implements IConfigCache, IPConfigCache
private $config;
/**
* @param array $config A initial config array
* @var bool
*/
public function __construct(array $config = [])
private $hidePasswordOutput;
/**
* @param array $config A initial config array
* @param bool $hidePasswordOutput True, if cache variables should take extra care of password values
*/
public function __construct(array $config = [], $hidePasswordOutput = true)
{
$this->hidePasswordOutput = $hidePasswordOutput;
$this->load($config);
}
@ -84,8 +93,13 @@ class ConfigCache implements IConfigCache, IPConfigCache
$this->config[$cat] = [];
}
$this->config[$cat][$key] = $value;
if ($this->hidePasswordOutput &&
$key == 'password' &&
!empty($value) && is_string($value)) {
$this->config[$cat][$key] = new HiddenString((string) $value);
} else {
$this->config[$cat][$key] = $value;
}
return true;
}

View file

@ -88,7 +88,7 @@ class Configuration
if (isset($dbvalue)) {
$this->configCache->set($cat, $key, $dbvalue);
return $dbvalue;
unset($dbvalue);
}
}

View file

@ -156,7 +156,7 @@ class Installer
'$basepath' => $basepath,
'$timezone' => $configCache->get('system', 'default_timezone'),
'$language' => $configCache->get('system', 'language'),
], false);
]);
$result = file_put_contents($basepath . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'local.config.php', $txt);

244
src/Core/Search.php Normal file
View file

@ -0,0 +1,244 @@
<?php
namespace Friendica\Core;
use Friendica\BaseObject;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Network\HTTPException;
use Friendica\Network\Probe;
use Friendica\Object\Search\ContactResult;
use Friendica\Object\Search\ResultList;
use Friendica\Protocol\PortableContact;
use Friendica\Util\Network;
use Friendica\Util\Strings;
/**
* Specific class to perform searches for different systems. Currently:
* - Probe for contacts
* - Search in the local directory
* - Search in the global directory
*/
class Search extends BaseObject
{
const DEFAULT_DIRECTORY = 'https://dir.friendica.social';
const TYPE_PEOPLE = 0;
const TYPE_FORUM = 1;
const TYPE_ALL = 2;
/**
* Search a user based on his/her profile address
* pattern: @username@domain.tld
*
* @param string $user The user to search for
*
* @return ResultList
* @throws HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function getContactsFromProbe($user)
{
$emptyResultList = new ResultList(1, 0, 1);
if ((filter_var($user, FILTER_VALIDATE_EMAIL) && Network::isEmailDomainValid($user)) ||
(substr(Strings::normaliseLink($user), 0, 7) == "http://")) {
$user_data = Probe::uri($user);
if (empty($user_data)) {
return $emptyResultList;
}
if (!(in_array($user_data["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA]))) {
return $emptyResultList;
}
$contactDetails = Contact::getDetailsByURL(defaults($user_data, 'url', ''), local_user());
$itemUrl = defaults($contactDetails, 'addr', defaults($user_data, 'url', ''));
$result = new ContactResult(
defaults($user_data, 'name', ''),
defaults($user_data, 'addr', ''),
$itemUrl,
defaults($user_data, 'url', ''),
defaults($user_data, 'photo', ''),
defaults($user_data, 'network', ''),
defaults($contactDetails, 'cid', 0),
0,
defaults($user_data, 'tags', '')
);
return new ResultList(1, 1, 1, [$result]);
} else {
return $emptyResultList;
}
}
/**
* Search in the global directory for occurrences of the search string
*
* @see https://github.com/friendica/friendica-directory/blob/master/docs/Protocol.md#search
*
* @param string $search
* @param int $type specific type of searching
* @param int $page
*
* @return ResultList
* @throws HTTPException\InternalServerErrorException
*/
public static function getContactsFromGlobalDirectory($search, $type = self::TYPE_ALL, $page = 1)
{
$config = self::getApp()->getConfig();
$server = $config->get('system', 'directory', self::DEFAULT_DIRECTORY);
$searchUrl = $server . '/search';
switch ($type) {
case self::TYPE_FORUM:
$searchUrl .= '/forum';
break;
case self::TYPE_PEOPLE:
$searchUrl .= '/people';
break;
}
$searchUrl .= '?q=' . urlencode($search);
if ($page > 1) {
$searchUrl .= '&page=' . $page;
}
$resultJson = Network::fetchUrl($searchUrl, false, 0, 'application/json');
$results = json_decode($resultJson, true);
$resultList = new ResultList(
defaults($results, 'page', 1),
defaults($results, 'count', 0),
defaults($results, 'itemsperpage', 30)
);
$profiles = defaults($results, 'profiles', []);
foreach ($profiles as $profile) {
$contactDetails = Contact::getDetailsByURL(defaults($profile, 'profile_url', ''), local_user());
$itemUrl = defaults($contactDetails, 'addr', defaults($profile, 'profile_url', ''));
$result = new ContactResult(
defaults($profile, 'name', ''),
defaults($profile, 'addr', ''),
$itemUrl,
defaults($profile, 'profile_url', ''),
defaults($profile, 'photo', ''),
Protocol::DFRN,
defaults($contactDetails, 'cid', 0),
0,
defaults($profile, 'tags', ''));
$resultList->addResult($result);
}
return $resultList;
}
/**
* Search in the local database for occurrences of the search string
*
* @param string $search
* @param int $type
* @param int $start
* @param int $itemPage
*
* @return ResultList
* @throws HTTPException\InternalServerErrorException
*/
public static function getContactsFromLocalDirectory($search, $type = self::TYPE_ALL, $start = 0, $itemPage = 80)
{
$config = self::getApp()->getConfig();
$diaspora = $config->get('system', 'diaspora_enabled') ? Protocol::DIASPORA : Protocol::DFRN;
$ostatus = !$config->get('system', 'ostatus_disabled') ? Protocol::OSTATUS : Protocol::DFRN;
$wildcard = Strings::escapeHtml('%' . $search . '%');
$count = DBA::count('gcontact', [
'NOT `hide`
AND `network` IN (?, ?, ?, ?)
AND ((`last_contact` >= `last_failure`) OR (`updated` >= `last_failure`))
AND (`url` LIKE ? OR `name` LIKE ? OR `location` LIKE ?
OR `addr` LIKE ? OR `about` LIKE ? OR `keywords` LIKE ?)
AND `community` = ?',
Protocol::ACTIVITYPUB, Protocol::DFRN, $ostatus, $diaspora,
$wildcard, $wildcard, $wildcard,
$wildcard, $wildcard, $wildcard,
($type === self::TYPE_FORUM),
]);
$resultList = new ResultList($start, $itemPage, $count);
if (empty($count)) {
return $resultList;
}
$data = DBA::select('gcontact', ['nurl'], [
'NOT `hide`
AND `network` IN (?, ?, ?, ?)
AND ((`last_contact` >= `last_failure`) OR (`updated` >= `last_failure`))
AND (`url` LIKE ? OR `name` LIKE ? OR `location` LIKE ?
OR `addr` LIKE ? OR `about` LIKE ? OR `keywords` LIKE ?)
AND `community` = ?',
Protocol::ACTIVITYPUB, Protocol::DFRN, $ostatus, $diaspora,
$wildcard, $wildcard, $wildcard,
$wildcard, $wildcard, $wildcard,
($type === self::TYPE_FORUM),
], [
'group_by' => ['nurl', 'updated'],
'limit' => [$start, $itemPage],
'order' => ['updated' => 'DESC']
]);
if (!DBA::isResult($data)) {
return $resultList;
}
while ($row = DBA::fetch($data)) {
if (PortableContact::alternateOStatusUrl($row["nurl"])) {
continue;
}
$urlParts = parse_url($row["nurl"]);
// Ignore results that look strange.
// For historic reasons the gcontact table does contain some garbage.
if (!empty($urlParts['query']) || !empty($urlParts['fragment'])) {
continue;
}
$contact = Contact::getDetailsByURL($row["nurl"], local_user());
if ($contact["name"] == "") {
$contact["name"] = end(explode("/", $urlParts["path"]));
}
$result = new ContactResult(
$contact["name"],
$contact["addr"],
$contact["addr"],
$contact["url"],
$contact["photo"],
$contact["network"],
$contact["cid"],
$contact["zid"],
$contact["keywords"]
);
$resultList->addResult($result);
}
DBA::close($data);
// Add found profiles from the global directory to the local directory
Worker::add(PRIORITY_LOW, 'DiscoverPoCo', "dirsearch", urlencode($search));
return $resultList;
}
}

View file

@ -10,6 +10,7 @@ use Friendica\Database\DBStructure;
use Friendica\Model\Photo;
use Friendica\Object\Image;
use Friendica\Util\Strings;
use Friendica\Worker\Delivery;
/**
* @brief UserImport class
@ -39,14 +40,21 @@ class UserImport
$tableColumns = DBStructure::getColumns($table);
$tcols = [];
$ttype = [];
// get a plain array of column names
foreach ($tableColumns as $tcol) {
$tcols[] = $tcol['Field'];
$ttype[$tcol['Field']] = $tcol['Type'];
}
// remove inexistent columns
foreach ($arr as $icol => $ival) {
if (!in_array($icol, $tcols)) {
unset($arr[$icol]);
continue;
}
if ($ttype[$icol] === 'datetime') {
$arr[$icol] = $ival ?? DBA::NULL_DATETIME;
}
}
}
@ -271,7 +279,7 @@ class UserImport
}
// send relocate messages
Worker::add(PRIORITY_HIGH, 'Notifier', 'relocate', $newuid);
Worker::add(PRIORITY_HIGH, 'Notifier', Delivery::RELOCATION, $newuid);
info(L10n::t("Done. You can now login with your username and password"));
$a->internalRedirect('login');

View file

@ -983,7 +983,7 @@ class Worker
}
$url = System::baseUrl()."/worker";
Network::fetchUrl($url, false, $redirects, 1);
Network::fetchUrl($url, false, 1);
}
/**
@ -1100,7 +1100,7 @@ class Worker
* @param (integer|array) priority or parameter array, strings are deprecated and are ignored
*
* next args are passed as $cmd command line
* or: Worker::add(PRIORITY_HIGH, "Notifier", "drop", $drop_id);
* or: Worker::add(PRIORITY_HIGH, "Notifier", Delivery::DELETION, $drop_id);
* or: Worker::add(array('priority' => PRIORITY_HIGH, 'dont_fork' => true), "CreateShadowEntry", $post_id);
*
* @return boolean "false" if proc_run couldn't be executed

View file

@ -45,6 +45,7 @@ class DBA
*/
private static $logger;
private static $server_info = '';
/** @var PDO|mysqli */
private static $connection;
private static $driver;
private static $error = false;
@ -288,6 +289,19 @@ class DBA
}
}
/**
* Removes every not whitelisted character from the identifier string
*
* @param string $identifier
*
* @return string sanitized identifier
* @throws \Exception
*/
private static function sanitizeIdentifier($identifier)
{
return preg_replace('/[^A-Za-z0-9_\-]+/', '', $identifier);
}
public static function escape($str) {
if (self::$connected) {
switch (self::$driver) {
@ -483,6 +497,7 @@ class DBA
break;
}
/** @var $stmt mysqli_stmt|PDOStatement */
if (!$stmt = self::$connection->prepare($sql)) {
$errorInfo = self::$connection->errorInfo();
self::$error = $errorInfo[2];
@ -872,6 +887,29 @@ class DBA
return $columns;
}
/**
* @brief Insert a row into a table
*
* @param string/array $table Table name
*
* @return string formatted and sanitzed table name
* @throws \Exception
*/
public static function formatTableName($table)
{
if (is_string($table)) {
return "`" . self::sanitizeIdentifier($table) . "`";
}
if (!is_array($table)) {
return '';
}
$scheme = key($table);
return "`" . self::sanitizeIdentifier($scheme) . "`.`" . self::sanitizeIdentifier($table[$scheme]) . "`";
}
/**
* @brief Insert a row into a table
*
@ -889,7 +927,7 @@ class DBA
return false;
}
$sql = "INSERT INTO `".self::escape($table)."` (`".implode("`, `", array_keys($param))."`) VALUES (".
$sql = "INSERT INTO " . self::formatTableName($table) . " (`".implode("`, `", array_keys($param))."`) VALUES (".
substr(str_repeat("?, ", count($param)), 0, -2).")";
if ($on_duplicate_update) {
@ -938,7 +976,7 @@ class DBA
self::$connection->autocommit(false);
}
$success = self::e("LOCK TABLES `".self::escape($table)."` WRITE");
$success = self::e("LOCK TABLES " . self::formatTableName($table) ." WRITE");
if (self::$driver == 'pdo') {
self::$connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
@ -1119,7 +1157,7 @@ class DBA
$callstack[$key] = true;
$table = self::escape($table);
$table = self::sanitizeIdentifier($table);
$commands[$key] = ['table' => $table, 'conditions' => $conditions];
@ -1272,8 +1310,6 @@ class DBA
return false;
}
$table = self::escape($table);
$condition_string = self::buildCondition($condition);
if (is_bool($old_fields)) {
@ -1306,7 +1342,7 @@ class DBA
return true;
}
$sql = "UPDATE `".$table."` SET `".
$sql = "UPDATE ". self::formatTableName($table) . " SET `".
implode("` = ?, `", array_keys($fields))."` = ?".$condition_string;
$params1 = array_values($fields);
@ -1367,12 +1403,10 @@ class DBA
*/
public static function select($table, array $fields = [], array $condition = [], array $params = [])
{
if ($table == '') {
if (empty($table)) {
return false;
}
$table = self::escape($table);
if (count($fields) > 0) {
$select_fields = "`" . implode("`, `", array_values($fields)) . "`";
} else {
@ -1383,7 +1417,7 @@ class DBA
$param_string = self::buildParameter($params);
$sql = "SELECT " . $select_fields . " FROM `" . $table . "`" . $condition_string . $param_string;
$sql = "SELECT " . $select_fields . " FROM " . self::formatTableName($table) . $condition_string . $param_string;
$result = self::p($sql, $condition);
@ -1410,13 +1444,13 @@ class DBA
*/
public static function count($table, array $condition = [])
{
if ($table == '') {
if (empty($table)) {
return false;
}
$condition_string = self::buildCondition($condition);
$sql = "SELECT COUNT(*) AS `count` FROM `".$table."`".$condition_string;
$sql = "SELECT COUNT(*) AS `count` FROM " . self::formatTableName($table) . $condition_string;
$row = self::fetchFirst($sql, $condition);
@ -1507,6 +1541,15 @@ class DBA
*/
public static function buildParameter(array $params = [])
{
$groupby_string = '';
if (isset($params['group_by'])) {
$groupby_string = " GROUP BY ";
foreach ($params['group_by'] as $fields) {
$groupby_string .= "`" . $fields . "`, ";
}
$groupby_string = substr($groupby_string, 0, -2);
}
$order_string = '';
if (isset($params['order'])) {
$order_string = " ORDER BY ";
@ -1531,7 +1574,7 @@ class DBA
$limit_string = " LIMIT " . intval($params['limit'][0]) . ", " . intval($params['limit'][1]);
}
return $order_string.$limit_string;
return $groupby_string . $order_string . $limit_string;
}
/**

View file

@ -6,6 +6,7 @@ use Friendica\Core\Config\Cache;
use Friendica\Database;
use Friendica\Util\Logger\VoidLogger;
use Friendica\Util\Profiler;
use ParagonIE\HiddenString\HiddenString;
class DBFactory
{
@ -45,7 +46,7 @@ class DBFactory
} else {
$db_user = $server['MYSQL_USER'];
}
$db_pass = (string) $server['MYSQL_PASSWORD'];
$db_pass = new HiddenString((string) $server['MYSQL_PASSWORD']);
$db_data = $server['MYSQL_DATABASE'];
}

View file

@ -26,6 +26,7 @@ class LoggerFactory
{
/**
* A list of classes, which shouldn't get logged
*
* @var array
*/
private static $ignoreClassList = [
@ -37,8 +38,8 @@ class LoggerFactory
/**
* Creates a new PSR-3 compliant logger instances
*
* @param string $channel The channel of the logger instance
* @param Configuration $config The config
* @param string $channel The channel of the logger instance
* @param Configuration $config The config
* @param Profiler $profiler The profiler of the app
*
* @return LoggerInterface The PSR-3 compliant logger instance
@ -55,8 +56,8 @@ class LoggerFactory
}
$introspection = new Introspection(self::$ignoreClassList);
$level = $config->get('system', 'loglevel');
$loglevel = self::mapLegacyConfigDebugLevel((string)$level);
$level = $config->get('system', 'loglevel');
$loglevel = self::mapLegacyConfigDebugLevel((string)$level);
switch ($config->get('system', 'logger_config', 'stream')) {
case 'monolog':
@ -71,7 +72,10 @@ class LoggerFactory
$stream = $config->get('system', 'logfile');
static::addStreamHandler($logger, $stream, $loglevel);
// just add a stream in case it's either writable or not file
if (!is_file($stream) || is_writable($stream)) {
static::addStreamHandler($logger, $stream, $loglevel);
}
break;
case 'syslog':
@ -81,7 +85,12 @@ class LoggerFactory
case 'stream':
default:
$stream = $config->get('system', 'logfile');
$logger = new StreamLogger($channel, $stream, $introspection, $loglevel);
// just add a stream in case it's either writable or not file
if (!is_file($stream) || is_writable($stream)) {
$logger = new StreamLogger($channel, $stream, $introspection, $loglevel);
} else {
$logger = new VoidLogger();
}
break;
}
@ -105,8 +114,8 @@ class LoggerFactory
*
* It should never get filled during normal usage of Friendica
*
* @param string $channel The channel of the logger instance
* @param Configuration $config The config
* @param string $channel The channel of the logger instance
* @param Configuration $config The config
* @param Profiler $profiler The profiler of the app
*
* @return LoggerInterface The PSR-3 compliant logger instance
@ -120,7 +129,8 @@ class LoggerFactory
$stream = $config->get('system', 'dlogfile');
$developerIp = $config->get('system', 'dlogip');
if (!isset($developerIp) || !$debugging) {
if ((!isset($developerIp) || !$debugging) &&
(!is_file($stream) || is_writable($stream))) {
$logger = new VoidLogger();
Logger::setDevLogger($logger);
return $logger;
@ -149,7 +159,7 @@ class LoggerFactory
break;
case 'syslog':
$logger = new SyslogLogger($channel, $introspection, LogLevel::DEBUG);
$logger = new SyslogLogger($channel, $introspection, LogLevel::DEBUG);
break;
case 'stream':
@ -172,6 +182,7 @@ class LoggerFactory
/**
* Mapping a legacy level to the PSR-3 compliant levels
*
* @see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md#5-psrlogloglevel
*
* @param string $level the level to be mapped
@ -208,9 +219,9 @@ class LoggerFactory
/**
* Adding a handler to a given logger instance
*
* @param LoggerInterface $logger The logger instance
* @param mixed $stream The stream which handles the logger output
* @param string $level The level, for which this handler at least should handle logging
* @param LoggerInterface $logger The logger instance
* @param mixed $stream The stream which handles the logger output
* @param string $level The level, for which this handler at least should handle logging
*
* @return void
*

View file

@ -9,6 +9,7 @@ namespace Friendica\Model;
use Friendica\BaseObject;
use Friendica\Content\Text\HTML;
use Friendica\Core\Logger;
use Friendica\Core\Config;
use Friendica\Database\DBA;
use Friendica\Protocol\ActivityPub;
use Friendica\Util\Network;
@ -22,21 +23,30 @@ 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
* @param string $url profile URL. When set then we return "true" when this profile url can be found at the address
* @return string|boolean url
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private static function addrToUrl($addr)
private static function addrToUrl($addr, $url = null)
{
$addr_parts = explode('@', $addr);
if (count($addr_parts) != 2) {
return false;
}
$xrd_timeout = Config::get('system', 'xrd_timeout');
$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']);
$curlResult = Network::curl($webfinger, false, ['timeout' => $xrd_timeout, 'accept_content' => 'application/jrd+json,application/json']);
if (!$curlResult->isSuccess() || empty($curlResult->getBody())) {
return false;
$webfinger = Strings::normaliseLink($webfinger);
$curlResult = Network::curl($webfinger, false, ['timeout' => $xrd_timeout, 'accept_content' => 'application/jrd+json,application/json']);
if (!$curlResult->isSuccess() || empty($curlResult->getBody())) {
return false;
}
}
$data = json_decode($curlResult->getBody(), true);
@ -46,11 +56,15 @@ class APContact extends BaseObject
}
foreach ($data['links'] as $link) {
if (!empty($url) && !empty($link['href']) && ($link['href'] == $url)) {
return true;
}
if (empty($link['href']) || empty($link['rel']) || empty($link['type'])) {
continue;
}
if (($link['rel'] == 'self') && ($link['type'] == 'application/activity+json')) {
if (empty($url) && ($link['rel'] == 'self') && ($link['type'] == 'application/activity+json')) {
return $link['href'];
}
}
@ -73,6 +87,8 @@ class APContact extends BaseObject
return false;
}
$fetched_contact = false;
if (empty($update)) {
if (is_null($update)) {
$ref_update = DateTimeFormat::utc('now - 1 month');
@ -96,24 +112,28 @@ class APContact extends BaseObject
if (!is_null($update)) {
return DBA::isResult($apcontact) ? $apcontact : false;
}
if (DBA::isResult($apcontact)) {
$fetched_contact = $apcontact;
}
}
if (empty(parse_url($url, PHP_URL_SCHEME))) {
$url = self::addrToUrl($url);
if (empty($url)) {
return false;
return $fetched_contact;
}
}
$data = ActivityPub::fetchContent($url);
if (empty($data)) {
return false;
return $fetched_contact;
}
$compacted = JsonLD::compact($data);
if (empty($compacted['@id'])) {
return false;
return $fetched_contact;
}
$apcontact = [];
@ -152,8 +172,14 @@ class APContact extends BaseObject
$apcontact['alias'] = JsonLD::fetchElement($compacted['as:url'], 'as:href', '@id');
}
if (empty($apcontact['url']) || empty($apcontact['inbox'])) {
return false;
// Quit if none of the basic values are set
if (empty($apcontact['url']) || empty($apcontact['inbox']) || empty($apcontact['type'])) {
return $fetched_contact;
}
// Quit if this doesn't seem to be an account at all
if (!in_array($apcontact['type'], ActivityPub::ACCOUNT_TYPES)) {
return $fetched_contact;
}
$parts = parse_url($apcontact['url']);
@ -183,11 +209,13 @@ class APContact extends BaseObject
// 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);
$parts = parse_url($apcontact['url']);
unset($parts['path']);
$baseurl = Network::unparseURL($parts);
// Check if the address is resolvable or the profile url is identical with the base url of the system
if (self::addrToUrl($apcontact['addr'], $apcontact['url']) || Strings::compareLink($apcontact['url'], $baseurl)) {
$apcontact['baseurl'] = $baseurl;
} else {
$apcontact['addr'] = null;
}
@ -204,6 +232,11 @@ class APContact extends BaseObject
DBA::update('apcontact', $apcontact, ['url' => $url], true);
// We delete the old entry when the URL is changed
if (($url != $apcontact['url']) && DBA::exists('apcontact', ['url' => $url]) && DBA::exists('apcontact', ['url' => $apcontact['url']])) {
DBA::delete('apcontact', ['url' => $url]);
}
// Update some data in the contact table with various ways to catch them all
$contact_fields = ['name' => $apcontact['name'], 'about' => $apcontact['about'], 'alias' => $apcontact['alias']];
@ -228,11 +261,13 @@ class APContact extends BaseObject
DBA::update('contact', $contact_fields, ['nurl' => Strings::normaliseLink($url)]);
$contacts = DBA::select('contact', ['uid', 'id'], ['nurl' => Strings::normaliseLink($url)]);
while ($contact = DBA::fetch($contacts)) {
Contact::updateAvatar($apcontact['photo'], $contact['uid'], $contact['id']);
if (!empty($apcontact['photo'])) {
$contacts = DBA::select('contact', ['uid', 'id'], ['nurl' => Strings::normaliseLink($url)]);
while ($contact = DBA::fetch($contacts)) {
Contact::updateAvatar($apcontact['photo'], $contact['uid'], $contact['id']);
}
DBA::close($contacts);
}
DBA::close($contacts);
// Update the gcontact table
// These two fields don't exist in the gcontact table

View file

@ -124,6 +124,20 @@ class Contact extends BaseObject
return DBA::toArray($statement);
}
/**
* @param array $fields Array of selected fields, empty for all
* @param array $condition Array of fields for condition
* @param array $params Array of several parameters
* @return array
* @throws \Exception
*/
public static function selectFirst(array $fields = [], array $condition = [], array $params = [])
{
$contact = DBA::selectFirst('contact', $fields, $condition, $params);
return $contact;
}
/**
* @param integer $id Contact ID
* @param array $fields Array of selected fields, empty for all
@ -579,11 +593,13 @@ class Contact extends BaseObject
return;
}
$file_suffix = 'jpg';
$fields = ['name' => $profile['name'], 'nick' => $user['nickname'],
'avatar-date' => $self['avatar-date'], 'location' => Profile::formatLocation($profile),
'about' => $profile['about'], 'keywords' => $profile['pub_keywords'],
'gender' => $profile['gender'], 'avatar' => $profile['photo'],
'contact-type' => $user['account-type'], 'xmpp' => $profile['xmpp']];
'gender' => $profile['gender'], 'contact-type' => $user['account-type'],
'xmpp' => $profile['xmpp']];
$avatar = Photo::selectFirst(['resource-id', 'type'], ['uid' => $uid, 'profile' => true]);
if (DBA::isResult($avatar)) {
@ -595,8 +611,6 @@ class Contact extends BaseObject
$types = Image::supportedTypes();
if (isset($types[$avatar['type']])) {
$file_suffix = $types[$avatar['type']];
} else {
$file_suffix = 'jpg';
}
// We are adding a timestamp value so that other systems won't use cached content
@ -615,6 +629,7 @@ class Contact extends BaseObject
$fields['micro'] = System::baseUrl() . '/images/person-48.jpg';
}
$fields['avatar'] = System::baseUrl() . '/photo/profile/' .$uid . '.' . $file_suffix;
$fields['forum'] = $user['page-flags'] == User::PAGE_FLAGS_COMMUNITY;
$fields['prv'] = $user['page-flags'] == User::PAGE_FLAGS_PRVGROUP;
@ -647,8 +662,8 @@ class Contact extends BaseObject
DBA::update('contact', $fields, ['uid' => 0, 'nurl' => $self['nurl']]);
// Update the profile
$fields = ['photo' => System::baseUrl() . '/photo/profile/' .$uid . '.jpg',
'thumb' => System::baseUrl() . '/photo/avatar/' . $uid .'.jpg'];
$fields = ['photo' => System::baseUrl() . '/photo/profile/' .$uid . '.' . $file_suffix,
'thumb' => System::baseUrl() . '/photo/avatar/' . $uid .'.' . $file_suffix];
DBA::update('profile', $fields, ['uid' => $uid, 'is-default' => true]);
}
}
@ -894,7 +909,7 @@ class Contact extends BaseObject
// If there is more than one entry we filter out the connector networks
if (count($r) > 1) {
foreach ($r as $id => $result) {
if ($result["network"] == Protocol::STATUSNET) {
if (!in_array($result["network"], Protocol::NATIVE_SUPPORT)) {
unset($r[$id]);
}
}
@ -1078,7 +1093,7 @@ class Contact extends BaseObject
$profile_link = $profile_link . '?tab=profile';
}
if (in_array($contact['network'], [Protocol::DFRN, Protocol::DIASPORA]) && !$contact['self']) {
if (self::canReceivePrivateMessages($contact)) {
$pm_url = System::baseUrl() . '/message/new/' . $contact['id'];
}
@ -1449,12 +1464,14 @@ class Contact extends BaseObject
return $contact_id;
}
$updated = ['addr' => $data['addr'],
$updated = [
'addr' => $data['addr'] ?? '',
'alias' => defaults($data, 'alias', ''),
'url' => $data['url'],
'nurl' => Strings::normaliseLink($data['url']),
'name' => $data['name'],
'nick' => $data['nick']];
'nick' => $data['nick']
];
if (!empty($data['keywords'])) {
$updated['keywords'] = $data['keywords'];
@ -1488,7 +1505,7 @@ class Contact extends BaseObject
$updated['pubkey'] = $data['pubkey'];
}
if (($data['addr'] != $contact['addr']) || (!empty($data['alias']) && ($data['alias'] != $contact['alias']))) {
if (($updated['addr'] != $contact['addr']) || (!empty($data['alias']) && ($data['alias'] != $contact['alias']))) {
$updated['uri-date'] = DateTimeFormat::utcNow();
}
if (($data["name"] != $contact["name"]) || ($data["nick"] != $contact["nick"])) {
@ -1709,7 +1726,7 @@ class Contact extends BaseObject
*/
public static function updateAvatar($avatar, $uid, $cid, $force = false)
{
$contact = DBA::selectFirst('contact', ['avatar', 'photo', 'thumb', 'micro', 'nurl'], ['id' => $cid]);
$contact = DBA::selectFirst('contact', ['avatar', 'photo', 'thumb', 'micro', 'nurl'], ['id' => $cid, 'self' => false]);
if (!DBA::isResult($contact)) {
return false;
} else {
@ -1720,17 +1737,14 @@ class Contact extends BaseObject
$photos = Photo::importProfilePhoto($avatar, $uid, $cid, true);
if ($photos) {
DBA::update(
'contact',
['avatar' => $avatar, 'photo' => $photos[0], 'thumb' => $photos[1], 'micro' => $photos[2], 'avatar-date' => DateTimeFormat::utcNow()],
['id' => $cid]
);
$fields = ['avatar' => $avatar, 'photo' => $photos[0], 'thumb' => $photos[1], 'micro' => $photos[2], 'avatar-date' => DateTimeFormat::utcNow()];
DBA::update('contact', $fields, ['id' => $cid]);
// Update the public contact (contact id = 0)
if ($uid != 0) {
$pcontact = DBA::selectFirst('contact', ['id'], ['nurl' => $contact['nurl'], 'uid' => 0]);
if (DBA::isResult($pcontact)) {
self::updateAvatar($avatar, 0, $pcontact['id'], $force);
DBA::update('contact', $fields, ['id' => $pcontact['id']]);
}
}
@ -2120,63 +2134,81 @@ class Contact extends BaseObject
return $contact;
}
public static function addRelationship($importer, $contact, $datarray, $item = '', $sharing = false, $note = '') {
/**
* @param array $importer Owner (local user) data
* @param array $contact Existing owner-specific contact data we want to expand the relationship with. Optional.
* @param array $datarray An item-like array with at least the 'author-id' and 'author-url' keys for the contact. Mandatory.
* @param bool $sharing True: Contact is now sharing with Owner; False: Contact is now following Owner (default)
* @param string $note Introduction additional message
* @return bool|null True: follow request is accepted; False: relationship is rejected; Null: relationship is pending
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function addRelationship(array $importer, array $contact, array $datarray, $sharing = false, $note = '')
{
// Should always be set
if (empty($datarray['author-id'])) {
return;
return false;
}
$fields = ['url', 'name', 'nick', 'photo', 'network'];
$fields = ['url', 'name', 'nick', 'avatar', 'photo', 'network', 'blocked'];
$pub_contact = DBA::selectFirst('contact', $fields, ['id' => $datarray['author-id']]);
if (!DBA::isResult($pub_contact)) {
// Should never happen
return;
return false;
}
// Contact is blocked at node-level
if (self::isBlocked($datarray['author-id'])) {
return false;
}
$url = defaults($datarray, 'author-link', $pub_contact['url']);
$name = $pub_contact['name'];
$photo = $pub_contact['photo'];
$photo = defaults($pub_contact, 'avatar', $pub_contact["photo"]);
$nick = $pub_contact['nick'];
$network = $pub_contact['network'];
if (is_array($contact)) {
if (!empty($contact)) {
// Contact is blocked at user-level
if (!empty($contact['id']) && !empty($importer['id']) &&
self::isBlockedByUser($contact['id'], $importer['id'])) {
return false;
}
// Make sure that the existing contact isn't archived
self::unmarkForArchival($contact);
$protocol = self::getProtocol($url, $contact['network']);
if (($contact['rel'] == self::SHARING)
|| ($sharing && $contact['rel'] == self::FOLLOWER)) {
DBA::update('contact', ['rel' => self::FRIEND, 'writable' => true, 'pending' => false],
['id' => $contact['id'], 'uid' => $importer['uid']]);
}
if ($protocol == Protocol::ACTIVITYPUB) {
ActivityPub\Transmitter::sendContactAccept($contact['url'], $contact['hub-verify'], $importer['uid']);
}
// send email notification to owner?
return true;
} else {
$protocol = self::getProtocol($url, $network);
// send email notification to owner?
if (DBA::exists('contact', ['nurl' => Strings::normaliseLink($url), 'uid' => $importer['uid'], 'pending' => true])) {
Logger::log('ignoring duplicated connection request from pending contact ' . $url);
return;
return null;
}
// create contact record
q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
`blocked`, `readonly`, `pending`, `writable`)
VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
intval($importer['uid']),
DBA::escape(DateTimeFormat::utcNow()),
DBA::escape($url),
DBA::escape(Strings::normaliseLink($url)),
DBA::escape($name),
DBA::escape($nick),
DBA::escape($photo),
DBA::escape($network),
intval(self::FOLLOWER)
);
DBA::insert('contact', [
'uid' => $importer['uid'],
'created' => DateTimeFormat::utcNow(),
'url' => $url,
'nurl' => Strings::normaliseLink($url),
'name' => $name,
'nick' => $nick,
'photo' => $photo,
'network' => $network,
'rel' => self::FOLLOWER,
'blocked' => 0,
'readonly' => 0,
'pending' => 1,
'writable' => 1,
]);
$contact_record = [
'id' => DBA::lastInsertId(),
@ -2220,20 +2252,16 @@ class Contact extends BaseObject
'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
'otype' => 'intro'
]);
}
} elseif (DBA::isResult($user) && in_array($user['page-flags'], [User::PAGE_FLAGS_SOAPBOX, User::PAGE_FLAGS_FREELOVE, User::PAGE_FLAGS_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']]);
$protocol = self::getProtocol($contact['url'], $contact['network']);
if ($protocol == Protocol::ACTIVITYPUB) {
ActivityPub\Transmitter::sendContactAccept($contact['url'], $contact['hub-verify'], $importer['uid']);
}
return true;
}
}
return null;
}
public static function removeFollower($importer, $contact, array $datarray = [], $item = "")
@ -2342,6 +2370,9 @@ class Contact extends BaseObject
return $url ?: $contact_url; // Equivalent to: ($url != '') ? $url : $contact_url;
}
// Prevents endless loop in case only a non-public contact exists for the contact URL
unset($data['uid']);
return self::magicLinkByContact($data, $contact_url);
}
@ -2431,4 +2462,18 @@ class Contact extends BaseObject
// Is it a forum?
return ($contact['forum'] || $contact['prv']);
}
/**
* Can the remote contact receive private messages?
*
* @param array $contact
* @return bool
*/
public static function canReceivePrivateMessages(array $contact)
{
$protocol = $contact['network'] ?? $contact['protocol'] ?? Protocol::PHANTOM;
$self = $contact['self'] ?? false;
return in_array($protocol, [Protocol::DFRN, Protocol::DIASPORA, Protocol::ACTIVITYPUB]) && !$self;
}
}

View file

@ -226,7 +226,7 @@ class Event extends BaseObject
return;
}
DBA::delete('event', ['id' => $event_id]);
DBA::delete('event', ['id' => $event_id], ['cascade' => false]);
Logger::log("Deleted event ".$event_id, Logger::DEBUG);
}

View file

@ -11,127 +11,149 @@ use Friendica\Database\DBA;
/**
* @brief This class handles FileTag related functions
*
* post categories and "save to file" use the same item.file table for storage.
* We will differentiate the different uses by wrapping categories in angle brackets
* and save to file categories in square brackets.
* To do this we need to escape these characters if they appear in our tag.
*/
class FileTag
{
// post categories and "save to file" use the same item.file table for storage.
// We will differentiate the different uses by wrapping categories in angle brackets
// and save to file categories in square brackets.
// To do this we need to escape these characters if they appear in our tag.
/**
* @brief URL encode <, >, left and right brackets
*
* @param string $s String to be URL encoded.
*
* @return string The URL encoded string.
*/
public static function encode($s)
{
return str_replace(['<', '>', '[', ']'], ['%3c', '%3e', '%5b', '%5d'], $s);
}
/**
* @brief URL encode &lt, &gt, left and right brackets
*
* @param string $s String to be URL encoded.
*
* @return string The URL encoded string.
*/
public static function encode($s)
{
return str_replace(['<', '>', '[', ']'], ['%3c', '%3e', '%5b', '%5d'], $s);
}
/**
* @brief URL decode <, >, left and right brackets
*
* @param string $s The URL encoded string to be decoded
*
* @return string The decoded string.
*/
public static function decode($s)
{
return str_replace(['%3c', '%3e', '%5b', '%5d'], ['<', '>', '[', ']'], $s);
}
/**
* @brief URL decode &lt, &gt, left and right brackets
*
* @param string $s The URL encoded string to be decoded
*
* @return string The decoded string.
*/
public static function decode($s)
{
return str_replace(['%3c', '%3e', '%5b', '%5d'], ['<', '>', '[', ']'], $s);
}
/**
* @brief Query files for tag
*
* @param string $table The table to be queired.
* @param string $s The search term
* @param string $type Optional file type.
*
* @return string Query string.
*/
public static function fileQuery($table, $s, $type = 'file')
{
if ($type == 'file') {
$str = preg_quote('[' . str_replace('%', '%%', self::encode($s)) . ']');
} else {
$str = preg_quote('<' . str_replace('%', '%%', self::encode($s)) . '>');
}
/**
* @brief Query files for tag
*
* @param string $table The table to be queired.
* @param string $s The search term
* @param string $type Optional file type.
*
* @return string Query string.
*/
public static function fileQuery($table, $s, $type = 'file')
{
if ($type == 'file') {
$str = preg_quote('[' . str_replace('%', '%%', self::encode($s)) . ']');
} else {
$str = preg_quote('<' . str_replace('%', '%%', self::encode($s)) . '>');
}
return " AND " . (($table) ? DBA::escape($table) . '.' : '') . "file regexp '" . DBA::escape($str) . "' ";
}
return " AND " . (($table) ? DBA::escape($table) . '.' : '') . "file regexp '" . DBA::escape($str) . "' ";
}
/**
* Get file tags from array
*
* ex. given [music,video] return <music><video> or [music][video]
*
* @param array $array A list of tags.
* @param string $type Optional file type.
*
* @return string A list of file tags.
*/
public static function arrayToFile(array $array, string $type = 'file')
{
$tag_list = '';
if ($type == 'file') {
$lbracket = '[';
$rbracket = ']';
} else {
$lbracket = '<';
$rbracket = '>';
}
/**
* @brief Get file tags from list
*
* ex. given music,video return <music><video> or [music][video]
* @param string $list A comma delimited list of tags.
* @param string $type Optional file type.
*
* @return string A list of file tags.
*/
public static function listToFile($list, $type = 'file')
{
$tag_list = '';
if (strlen($list)) {
$list_array = explode(",", $list);
if ($type == 'file') {
$lbracket = '[';
$rbracket = ']';
} else {
$lbracket = '<';
$rbracket = '>';
}
foreach ($array as $item) {
if (strlen($item)) {
$tag_list .= $lbracket . self::encode(trim($item)) . $rbracket;
}
}
foreach ($list_array as $item)
{
if (strlen($item))
{
$tag_list .= $lbracket . self::encode(trim($item)) . $rbracket;
}
}
}
return $tag_list;
}
return $tag_list;
}
/**
* Get tag list from file tags
*
* ex. given <music><video>[friends], return [music,video] or [friends]
*
* @param string $file File tags
* @param string $type Optional file type.
*
* @return array List of tag names.
*/
public static function fileToArray(string $file, string $type = 'file')
{
$matches = [];
$return = [];
/**
* @brief Get list from file tags
*
* ex. given <music><video>[friends], return music,video or friends
* @param string $file File tags
* @param string $type Optional file type.
*
* @return string Comma delimited list of tag names.
*/
public static function fileToList($file, $type = 'file')
{
$matches = false;
$list = '';
if ($type == 'file') {
$cnt = preg_match_all('/\[(.*?)\]/', $file, $matches, PREG_SET_ORDER);
} else {
$cnt = preg_match_all('/<(.*?)>/', $file, $matches, PREG_SET_ORDER);
}
if ($type == 'file') {
$cnt = preg_match_all('/\[(.*?)\]/', $file, $matches, PREG_SET_ORDER);
} else {
$cnt = preg_match_all('/<(.*?)>/', $file, $matches, PREG_SET_ORDER);
}
if ($cnt) {
foreach ($matches as $match) {
$return[] = self::decode($match[1]);
}
}
if ($cnt)
{
foreach ($matches as $mtch)
{
if (strlen($list))
{
$list .= ',';
}
return $return;
}
$list .= self::decode($mtch[1]);
}
}
/**
* @brief Get file tags from list
*
* ex. given music,video return <music><video> or [music][video]
* @param string $list A comma delimited list of tags.
* @param string $type Optional file type.
*
* @return string A list of file tags.
* @deprecated since 2019.06 use arrayToFile() instead
*/
public static function listToFile(string $list, string $type = 'file')
{
$list_array = explode(',', $list);
return $list;
}
return self::arrayToFile($list_array, $type);
}
/**
* @brief Get list from file tags
*
* ex. given <music><video>[friends], return music,video or friends
* @param string $file File tags
* @param string $type Optional file type.
*
* @return string Comma delimited list of tag names.
* @deprecated since 2019.06 use fileToArray() instead
*/
public static function fileToList(string $file, $type = 'file')
{
return implode(',', self::fileToArray($file, $type));
}
/**
* @brief Update file tags in PConfig
@ -142,83 +164,74 @@ class FileTag
* @param string $type Optional file type.
*
* @return boolean A value indicating success or failure.
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \Exception
*/
public static function updatePconfig($uid, $file_old, $file_new, $type = 'file')
{
if (!intval($uid)) {
return false;
} elseif ($file_old == $file_new) {
return true;
}
public static function updatePconfig(int $uid, string $file_old, string $file_new, string $type = 'file')
{
if (!intval($uid)) {
return false;
} elseif ($file_old == $file_new) {
return true;
}
$saved = PConfig::get($uid, 'system', 'filetags');
$saved = PConfig::get($uid, 'system', 'filetags');
if (strlen($saved))
{
if ($type == 'file') {
$lbracket = '[';
$rbracket = ']';
$termtype = TERM_FILE;
} else {
$lbracket = '<';
$rbracket = '>';
$termtype = TERM_CATEGORY;
}
if (strlen($saved)) {
if ($type == 'file') {
$lbracket = '[';
$rbracket = ']';
$termtype = TERM_FILE;
} else {
$lbracket = '<';
$rbracket = '>';
$termtype = TERM_CATEGORY;
}
$filetags_updated = $saved;
$filetags_updated = $saved;
// check for new tags to be added as filetags in pconfig
$new_tags = [];
$check_new_tags = explode(",", self::fileToList($file_new, $type));
// check for new tags to be added as filetags in pconfig
$new_tags = [];
foreach (self::fileToArray($file_new, $type) as $tag) {
if (!stristr($saved, $lbracket . self::encode($tag) . $rbracket)) {
$new_tags[] = $tag;
}
}
foreach ($check_new_tags as $tag)
{
if (!stristr($saved,$lbracket . self::encode($tag) . $rbracket)) {
$new_tags[] = $tag;
}
}
$filetags_updated .= self::arrayToFile($new_tags, $type);
$filetags_updated .= self::listToFile(implode(",", $new_tags), $type);
// check for deleted tags to be removed from filetags in pconfig
$deleted_tags = [];
foreach (self::fileToArray($file_old, $type) as $tag) {
if (!stristr($file_new, $lbracket . self::encode($tag) . $rbracket)) {
$deleted_tags[] = $tag;
}
}
// check for deleted tags to be removed from filetags in pconfig
$deleted_tags = [];
$check_deleted_tags = explode(",", self::fileToList($file_old, $type));
foreach ($deleted_tags as $key => $tag) {
$r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
DBA::escape($tag),
intval(Term::OBJECT_TYPE_POST),
intval($termtype),
intval($uid));
foreach ($check_deleted_tags as $tag)
{
if (!stristr($file_new,$lbracket . self::encode($tag) . $rbracket)) {
$deleted_tags[] = $tag;
}
}
if (DBA::isResult($r)) {
unset($deleted_tags[$key]);
} else {
$filetags_updated = str_replace($lbracket . self::encode($tag) . $rbracket, '', $filetags_updated);
}
}
foreach ($deleted_tags as $key => $tag)
{
$r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
DBA::escape($tag),
intval(TERM_OBJ_POST),
intval($termtype),
intval($uid));
if ($saved != $filetags_updated) {
PConfig::set($uid, 'system', 'filetags', $filetags_updated);
}
if (DBA::isResult($r)) {
unset($deleted_tags[$key]);
} else {
$filetags_updated = str_replace($lbracket . self::encode($tag) . $rbracket, '', $filetags_updated);
}
}
return true;
} elseif (strlen($file_new)) {
PConfig::set($uid, 'system', 'filetags', $file_new);
}
if ($saved != $filetags_updated)
{
PConfig::set($uid, 'system', 'filetags', $filetags_updated);
}
return true;
} elseif (strlen($file_new)) {
PConfig::set($uid, 'system', 'filetags', $file_new);
}
return true;
}
return true;
}
/**
* @brief Add tag to file
@ -230,34 +243,30 @@ class FileTag
* @return boolean A value indicating success or failure.
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function saveFile($uid, $item_id, $file)
{
if (!intval($uid))
{
return false;
}
public static function saveFile($uid, $item_id, $file)
{
if (!intval($uid)) {
return false;
}
$item = Item::selectFirst(['file'], ['id' => $item_id, 'uid' => $uid]);
if (DBA::isResult($item))
{
if (!stristr($item['file'], '[' . self::encode($file) . ']'))
{
$fields = ['file' => $item['file'] . '[' . self::encode($file) . ']'];
Item::update($fields, ['id' => $item_id]);
}
$item = Item::selectFirst(['file'], ['id' => $item_id, 'uid' => $uid]);
if (DBA::isResult($item)) {
if (!stristr($item['file'], '[' . self::encode($file) . ']')) {
$fields = ['file' => $item['file'] . '[' . self::encode($file) . ']'];
Item::update($fields, ['id' => $item_id]);
}
$saved = PConfig::get($uid, 'system', 'filetags');
$saved = PConfig::get($uid, 'system', 'filetags');
if (!strlen($saved) || !stristr($saved, '[' . self::encode($file) . ']'))
{
PConfig::set($uid, 'system', 'filetags', $saved . '[' . self::encode($file) . ']');
}
if (!strlen($saved) || !stristr($saved, '[' . self::encode($file) . ']')) {
PConfig::set($uid, 'system', 'filetags', $saved . '[' . self::encode($file) . ']');
}
info(L10n::t('Item filed'));
}
info(L10n::t('Item filed'));
}
return true;
}
return true;
}
/**
* @brief Remove tag from file
@ -270,45 +279,42 @@ class FileTag
* @return boolean A value indicating success or failure.
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function unsaveFile($uid, $item_id, $file, $cat = false)
{
if (!intval($uid))
{
return false;
}
public static function unsaveFile($uid, $item_id, $file, $cat = false)
{
if (!intval($uid)) {
return false;
}
if ($cat == true) {
$pattern = '<' . self::encode($file) . '>';
$termtype = TERM_CATEGORY;
} else {
$pattern = '[' . self::encode($file) . ']';
$termtype = TERM_FILE;
}
if ($cat == true) {
$pattern = '<' . self::encode($file) . '>';
$termtype = Term::CATEGORY;
} else {
$pattern = '[' . self::encode($file) . ']';
$termtype = Term::FILE;
}
$item = Item::selectFirst(['file'], ['id' => $item_id, 'uid' => $uid]);
$item = Item::selectFirst(['file'], ['id' => $item_id, 'uid' => $uid]);
if (!DBA::isResult($item))
{
return false;
}
if (!DBA::isResult($item)) {
return false;
}
$fields = ['file' => str_replace($pattern, '', $item['file'])];
$fields = ['file' => str_replace($pattern, '', $item['file'])];
Item::update($fields, ['id' => $item_id]);
Item::update($fields, ['id' => $item_id]);
$r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
DBA::escape($file),
intval(TERM_OBJ_POST),
intval($termtype),
intval($uid)
);
$r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
DBA::escape($file),
intval(Term::OBJECT_TYPE_POST),
intval($termtype),
intval($uid)
);
if (!DBA::isResult($r))
{
$saved = PConfig::get($uid, 'system', 'filetags');
PConfig::set($uid, 'system', 'filetags', str_replace($pattern, '', $saved));
}
if (!DBA::isResult($r)) {
$saved = PConfig::get($uid, 'system', 'filetags');
PConfig::set($uid, 'system', 'filetags', str_replace($pattern, '', $saved));
}
return true;
}
return true;
}
}

View file

@ -11,9 +11,9 @@ use Friendica\Content\Text\BBCode;
use Friendica\Content\Text\HTML;
use Friendica\Core\Config;
use Friendica\Core\Hook;
use Friendica\Core\L10n;
use Friendica\Core\Lock;
use Friendica\Core\Logger;
use Friendica\Core\L10n;
use Friendica\Core\PConfig;
use Friendica\Core\Protocol;
use Friendica\Core\Renderer;
@ -24,10 +24,11 @@ use Friendica\Protocol\Diaspora;
use Friendica\Protocol\OStatus;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Map;
use Friendica\Util\XML;
use Friendica\Util\Network;
use Friendica\Util\Security;
use Friendica\Util\Strings;
use Friendica\Util\Network;
use Friendica\Util\XML;
use Friendica\Worker\Delivery;
use Text_LanguageDetect;
class Item extends BaseObject
@ -87,7 +88,7 @@ class Item extends BaseObject
'unseen', 'deleted', 'origin', 'forum_mode', 'mention', 'global', 'network',
'title', 'content-warning', 'body', 'location', 'coord', 'app',
'rendered-hash', 'rendered-html', 'object-type', 'object', 'target-type', 'target',
'author-id', 'author-link', 'author-name', 'author-avatar',
'author-id', 'author-link', 'author-name', 'author-avatar', 'author-network',
'owner-id', 'owner-link', 'owner-name', 'owner-avatar'];
// Never reorder or remove entries from this list. Just add new ones at the end, if needed.
@ -925,7 +926,7 @@ class Item extends BaseObject
// We only need to notfiy others when it is an original entry from us.
// Only call the notifier when the item has some content relevant change.
if ($item['origin'] && in_array('edited', array_keys($fields))) {
Worker::add(PRIORITY_HIGH, "Notifier", 'edit_post', $item['id']);
Worker::add(PRIORITY_HIGH, "Notifier", Delivery::POST, $item['id']);
}
}
@ -1079,9 +1080,11 @@ class Item extends BaseObject
}
// When the permission set will be used in photo and events as well,
// this query here needs to be extended.
if (!empty($item['psid']) && !self::exists(['psid' => $item['psid'], 'deleted' => false])) {
DBA::delete('permissionset', ['id' => $item['psid']], ['cascade' => false]);
}
// @todo Currently deactivated. We need the permission set in the deletion process.
// This is a reminder to add the removal somewhere else.
//if (!empty($item['psid']) && !self::exists(['psid' => $item['psid'], 'deleted' => false])) {
// DBA::delete('permissionset', ['id' => $item['psid']], ['cascade' => false]);
//}
// If it's the parent of a comment thread, kill all the kids
if ($item['id'] == $item['parent']) {
@ -1095,7 +1098,7 @@ class Item extends BaseObject
self::delete(['uri' => $item['uri'], 'deleted' => false], $priority);
// send the notification upstream/downstream
Worker::add(['priority' => $priority, 'dont_fork' => true], "Notifier", "drop", intval($item['id']));
Worker::add(['priority' => $priority, 'dont_fork' => true], "Notifier", Delivery::DELETION, intval($item['id']));
} elseif ($item['uid'] != 0) {
// When we delete just our local user copy of an item, we have to set a marker to hide it
@ -1452,6 +1455,11 @@ class Item extends BaseObject
return 0;
}
if (!empty($uid) && Contact::isBlockedByUser($item['author-id'], $uid)) {
Logger::notice('Author is blocked by user', ['author-link' => $item['author-link'], 'uid' => $uid, 'item-uri' => $item['uri']]);
return 0;
}
$default = ['url' => $item['owner-link'], 'name' => $item['owner-name'],
'photo' => $item['owner-avatar'], 'network' => $item['network']];
@ -1467,6 +1475,26 @@ class Item extends BaseObject
return 0;
}
if (!empty($uid) && Contact::isBlockedByUser($item['owner-id'], $uid)) {
Logger::notice('Owner is blocked by user', ['owner-link' => $item['owner-link'], 'uid' => $uid, 'item-uri' => $item['uri']]);
return 0;
}
// The causer is set during a thread completion, for example because of a reshare. It countains the responsible actor.
if (!empty($uid) && !empty($item['causer-id']) && Contact::isBlockedByUser($item['causer-id'], $uid)) {
Logger::notice('Causer is blocked by user', ['causer-link' => $item['causer-link'], 'uid' => $uid, 'item-uri' => $item['uri']]);
return 0;
}
if (!empty($uid) && !empty($item['causer-id']) && ($item['parent-uri'] == $item['uri']) && Contact::isIgnoredByUser($item['causer-id'], $uid)) {
Logger::notice('Causer is ignored by user', ['causer-link' => $item['causer-link'], 'uid' => $uid, 'item-uri' => $item['uri']]);
return 0;
}
// We don't store the causer, we only have it here for the checks above
unset($item['causer-id']);
unset($item['causer-link']);
if ($item['network'] == Protocol::PHANTOM) {
$item['network'] = Protocol::DFRN;
Logger::notice('Missing network, setting to {network}.', [
@ -1508,7 +1536,7 @@ class Item extends BaseObject
$item['thr-parent'] = $item['parent-uri'];
$notify_type = '';
$notify_type = Delivery::POST;
$allow_cid = '';
$allow_gid = '';
$deny_cid = '';
@ -1521,7 +1549,6 @@ class Item extends BaseObject
$allow_gid = $item['allow_gid'];
$deny_cid = $item['deny_cid'];
$deny_gid = $item['deny_gid'];
$notify_type = 'wall-new';
} else {
// find the parent and snarf the item id and ACLs
// and anything else we need to inherit
@ -1558,8 +1585,7 @@ class Item extends BaseObject
$allow_gid = $parent['allow_gid'];
$deny_cid = $parent['deny_cid'];
$deny_gid = $parent['deny_gid'];
$item['wall'] = $parent['wall'];
$notify_type = 'comment-new';
$item['wall'] = $parent['wall'];
/*
* If the parent is private, force privacy for the entire conversation
@ -1603,6 +1629,10 @@ class Item extends BaseObject
}
}
if (stristr($item['verb'], ACTIVITY_POKE)) {
$notify_type = Delivery::POKE;
}
$item['parent-uri-id'] = ItemURI::getIdByURI($item['parent-uri']);
$item['thr-parent-id'] = ItemURI::getIdByURI($item['thr-parent']);
@ -1721,6 +1751,7 @@ class Item extends BaseObject
unset($item['author-link']);
unset($item['author-name']);
unset($item['author-avatar']);
unset($item['author-network']);
unset($item['owner-link']);
unset($item['owner-name']);
@ -1876,18 +1907,8 @@ class Item extends BaseObject
check_user_notification($current_post);
if ($notify) {
if ($notify || ($item['visible'] && ((!empty($parent) && $parent['origin']) || $item['origin']))) {
Worker::add(['priority' => $priority, 'dont_fork' => true], 'Notifier', $notify_type, $current_post);
} elseif ($item['visible'] && ((!empty($parent) && $parent['origin']) || $item['origin'])) {
if ($item['gravity'] == GRAVITY_ACTIVITY) {
$cmd = $item['origin'] ? 'activity-new' : 'activity-import';
} elseif ($item['gravity'] == GRAVITY_COMMENT) {
$cmd = $item['origin'] ? 'comment-new' : 'comment-import';
} else {
$cmd = 'wall-new';
}
Worker::add(['priority' => $priority, 'dont_fork' => true], 'Notifier', $cmd, $current_post);
}
return $current_post;
@ -2603,7 +2624,7 @@ class Item extends BaseObject
self::updateThread($item_id);
Worker::add(['priority' => PRIORITY_HIGH, 'dont_fork' => true], 'Notifier', 'tgroup', $item_id);
Worker::add(['priority' => PRIORITY_HIGH, 'dont_fork' => true], 'Notifier', Delivery::POST, $item_id);
}
public static function isRemoteSelf($contact, &$datarray)
@ -2653,7 +2674,6 @@ class Item extends BaseObject
$datarray['author-link'] = $datarray['owner-link'];
$datarray['author-avatar'] = $datarray['owner-avatar'];
unset($datarray['created']);
unset($datarray['edited']);
unset($datarray['network']);

View file

@ -13,6 +13,7 @@ use Friendica\Model\Item;
use Friendica\Database\DBA;
use Friendica\Network\Probe;
use Friendica\Util\DateTimeFormat;
use Friendica\Worker\Delivery;
/**
* Class to handle private messages
@ -45,6 +46,8 @@ class Mail
$msg['guid'] = Item::guidFromUri($msg['uri'], $host);
}
$msg['created'] = (!empty($msg['created']) ? DateTimeFormat::utc($msg['created']) : DateTimeFormat::utcNow());
DBA::lock('mail');
if (DBA::exists('mail', ['uri' => $msg['uri'], 'uid' => $msg['uid']])) {
@ -72,7 +75,7 @@ class Mail
'to_email' => $user['email'],
'uid' => $user['uid'],
'item' => $msg,
'parent' => $msg['parent-uri'],
'parent' => 0,
'source_name' => $msg['from-name'],
'source_link' => $msg['from-url'],
'source_photo' => $msg['from-photo'],
@ -218,7 +221,7 @@ class Mail
}
if ($post_id) {
Worker::add(PRIORITY_HIGH, "Notifier", "mail", $post_id);
Worker::add(PRIORITY_HIGH, "Notifier", Delivery::MAIL, $post_id);
return intval($post_id);
} else {
return -3;

View file

@ -16,6 +16,7 @@ use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use Friendica\Model\Storage\IStorage;
use Friendica\Object\Image;
use Friendica\Protocol\DFRN;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Network;
use Friendica\Util\Security;
@ -133,8 +134,16 @@ class Photo extends BaseObject
if ($r === false) {
return false;
}
$uid = $r["uid"];
$sql_acl = Security::getPermissionsSQLByUserId($r["uid"]);
// This is the first place, when retrieving just a photo, that we know who owns the photo.
// Make sure that the requester's session is appropriately authenticated to that user
// otherwise permissions checks done by getPermissionsSQLByUserId() won't work correctly
$r = DBA::selectFirst("user", ["nickname"], ["uid" => $uid], []);
// this will either just return (if auth all ok) or will redirect and exit (starting over)
DFRN::autoRedir(self::getApp(), $r["nickname"]);
$sql_acl = Security::getPermissionsSQLByUserId($uid);
$conditions = [
"`resource-id` = ? AND `scale` <= ? " . $sql_acl,
@ -413,13 +422,22 @@ class Photo extends BaseObject
$photo_failure = false;
$filename = basename($image_url);
$img_str = Network::fetchUrl($image_url, true);
if (!empty($image_url)) {
$ret = Network::curl($image_url, true);
$img_str = $ret->getBody();
$type = $ret->getContentType();
} else {
$img_str = '';
}
if ($quit_on_error && ($img_str == "")) {
return false;
}
$type = Image::guessType($image_url, true);
if (empty($type)) {
$type = Image::guessType($image_url, true);
}
$Image = new Image($img_str, $type);
if ($Image->isValid()) {
$Image->scaleToSquare(300);

View file

@ -18,6 +18,7 @@ use Friendica\Core\Logger;
use Friendica\Core\PConfig;
use Friendica\Core\Protocol;
use Friendica\Core\Renderer;
use Friendica\Core\Session;
use Friendica\Core\System;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
@ -116,8 +117,13 @@ class Profile
}
if (count($profiledata) > 0) {
// Ensure to have a "nickname" field
if (empty($profiledata['nickname']) && !empty($profiledata['nick'])) {
$profiledata['nickname'] = $profiledata['nick'];
}
// Add profile data to sidebar
$a->page['aside'] .= self::sidebar($profiledata, true, $show_connect);
$a->page['aside'] .= self::sidebar($a, $profiledata, true, $show_connect);
if (!DBA::isResult($user)) {
return;
@ -188,7 +194,7 @@ class Profile
* But: When this profile was on the same server, then we could display the contacts
*/
if (!$profiledata) {
$a->page['aside'] .= self::sidebar($a->profile, $block, $show_connect);
$a->page['aside'] .= self::sidebar($a, $a->profile, $block, $show_connect);
}
return;
@ -280,10 +286,8 @@ class Profile
* @hooks 'profile_sidebar'
* array $arr
*/
private static function sidebar($profile, $block = 0, $show_connect = true)
private static function sidebar(App $a, $profile, $block = 0, $show_connect = true)
{
$a = \get_app();
$o = '';
$location = false;
@ -304,127 +308,102 @@ class Profile
Hook::callAll('profile_sidebar_enter', $profile);
if (isset($profile['url'])) {
$profile_url = $profile['url'];
} else {
$profile_url = $a->getBaseURL() . '/profile/' . $profile['nickname'];
}
// don't show connect link to yourself
$connect = $profile['uid'] != local_user() ? L10n::t('Connect') : false;
$follow_link = null;
$unfollow_link = null;
$subscribe_feed_link = null;
$wallmessage_link = null;
// don't show connect link to authenticated visitors either
if (remote_user() && !empty($_SESSION['remote'])) {
foreach ($_SESSION['remote'] as $visitor) {
if ($visitor['uid'] == $profile['uid']) {
$connect = false;
break;
$visitor_contact = [];
if (!empty($profile['uid']) && self::getMyURL()) {
$visitor_contact = Contact::selectFirst(['rel'], ['uid' => $profile['uid'], 'nurl' => Strings::normaliseLink(self::getMyURL())]);
}
$profile_contact = [];
if (!empty($profile['cid']) && self::getMyURL()) {
$profile_contact = Contact::selectFirst(['rel'], ['id' => $profile['cid']]);
}
$profile_is_dfrn = $profile['network'] == Protocol::DFRN;
$profile_is_native = in_array($profile['network'], Protocol::NATIVE_SUPPORT);
$local_user_is_self = local_user() && local_user() == ($profile['profile_uid'] ?? 0);
$visitor_is_authenticated = (bool)self::getMyURL();
$visitor_is_following =
in_array($visitor_contact['rel'] ?? 0, [Contact::FOLLOWER, Contact::FRIEND])
|| in_array($profile_contact['rel'] ?? 0, [Contact::SHARING, Contact::FRIEND]);
$visitor_is_followed =
in_array($visitor_contact['rel'] ?? 0, [Contact::SHARING, Contact::FRIEND])
|| in_array($profile_contact['rel'] ?? 0, [Contact::FOLLOWER, Contact::FRIEND]);
$visitor_base_path = self::getMyURL() ? preg_replace('=/profile/(.*)=ism', '', self::getMyURL()) : '';
if (!$local_user_is_self && $show_connect) {
if (!$visitor_is_authenticated) {
$follow_link = 'dfrn_request/' . $profile['nickname'];
} elseif ($profile_is_native) {
if ($visitor_is_following) {
$unfollow_link = $visitor_base_path . '/unfollow?url=' . urlencode($profile_url);
} else {
$follow_link = $visitor_base_path .'/follow?url=' . urlencode($profile_url);
}
}
}
if (!$show_connect) {
$connect = false;
}
$profile_url = '';
// Is the local user already connected to that user?
if ($connect && local_user()) {
if (isset($profile['url'])) {
$profile_url = Strings::normaliseLink($profile['url']);
} else {
$profile_url = Strings::normaliseLink(System::baseUrl() . '/profile/' . $profile['nickname']);
if ($profile_is_dfrn) {
$subscribe_feed_link = 'dfrn_poll/' . $profile['nickname'];
}
if (DBA::exists('contact', ['pending' => false, 'uid' => local_user(), 'nurl' => $profile_url])) {
$connect = false;
}
}
// Is the remote user already connected to that user?
if ($connect && Contact::isFollower(remote_user(), $profile['uid'])) {
$connect = false;
}
if ($connect && ($profile['network'] != Protocol::DFRN) && !isset($profile['remoteconnect'])) {
$connect = false;
}
$remoteconnect = null;
if (isset($profile['remoteconnect'])) {
$remoteconnect = $profile['remoteconnect'];
}
if ($connect && ($profile['network'] == Protocol::DFRN) && !isset($remoteconnect)) {
$subscribe_feed = L10n::t('Atom feed');
} else {
$subscribe_feed = false;
}
$wallmessage = false;
$wallmessage_link = false;
// See issue https://github.com/friendica/friendica/issues/3838
// Either we remove the message link for remote users or we enable creating messages from remote users
if (remote_user() || (self::getMyURL() && !empty($profile['unkmail']) && ($profile['uid'] != local_user()))) {
$wallmessage = L10n::t('Message');
if (remote_user()) {
$r = q(
"SELECT `url` FROM `contact` WHERE `uid` = %d AND `id` = '%s' AND `rel` = %d",
intval($profile['uid']),
intval(remote_user()),
intval(Contact::FRIEND)
);
} else {
$r = q(
"SELECT `url` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `rel` = %d",
intval($profile['uid']),
DBA::escape(Strings::normaliseLink(self::getMyURL())),
intval(Contact::FRIEND)
);
}
if ($r) {
$remote_url = $r[0]['url'];
$message_path = preg_replace('=(.*)/profile/(.*)=ism', '$1/message/new/', $remote_url);
$wallmessage_link = $message_path . base64_encode(defaults($profile, 'addr', ''));
} else if (!empty($profile['nickname'])) {
$wallmessage_link = 'wallmessage/' . $profile['nickname'];
if (Contact::canReceivePrivateMessages($profile)) {
if ($visitor_is_followed || $visitor_is_following) {
$wallmessage_link = $visitor_base_path . '/message/new/' . base64_encode(defaults($profile, 'addr', ''));
} elseif ($visitor_is_authenticated && !empty($profile['unkmail'])) {
$wallmessage_link = 'wallmessage/' . $profile['nickname'];
}
}
}
// show edit profile to yourself
if (!$is_contact && $profile['uid'] == local_user() && Feature::isEnabled(local_user(), 'multi_profiles')) {
$profile['edit'] = [System::baseUrl() . '/profiles', L10n::t('Profiles'), '', L10n::t('Manage/edit profiles')];
$r = q(
"SELECT * FROM `profile` WHERE `uid` = %d",
local_user()
);
if (!$is_contact && $local_user_is_self) {
if (Feature::isEnabled(local_user(), 'multi_profiles')) {
$profile['edit'] = [System::baseUrl() . '/profiles', L10n::t('Profiles'), '', L10n::t('Manage/edit profiles')];
$r = q(
"SELECT * FROM `profile` WHERE `uid` = %d",
local_user()
);
$profile['menu'] = [
'chg_photo' => L10n::t('Change profile photo'),
'cr_new' => L10n::t('Create New Profile'),
'entries' => [],
];
$profile['menu'] = [
'chg_photo' => L10n::t('Change profile photo'),
'cr_new' => L10n::t('Create New Profile'),
'entries' => [],
];
if (DBA::isResult($r)) {
foreach ($r as $rr) {
$profile['menu']['entries'][] = [
'photo' => $rr['thumb'],
'id' => $rr['id'],
'alt' => L10n::t('Profile Image'),
'profile_name' => $rr['profile-name'],
'isdefault' => $rr['is-default'],
'visibile_to_everybody' => L10n::t('visible to everybody'),
'edit_visibility' => L10n::t('Edit visibility'),
];
if (DBA::isResult($r)) {
foreach ($r as $rr) {
$profile['menu']['entries'][] = [
'photo' => $rr['thumb'],
'id' => $rr['id'],
'alt' => L10n::t('Profile Image'),
'profile_name' => $rr['profile-name'],
'isdefault' => $rr['is-default'],
'visibile_to_everybody' => L10n::t('visible to everybody'),
'edit_visibility' => L10n::t('Edit visibility'),
];
}
}
} else {
$profile['edit'] = [System::baseUrl() . '/profiles/' . $profile['id'], L10n::t('Edit profile'), '', L10n::t('Edit profile')];
$profile['menu'] = [
'chg_photo' => L10n::t('Change profile photo'),
'cr_new' => null,
'entries' => [],
];
}
}
if (!$is_contact && $profile['uid'] == local_user() && !Feature::isEnabled(local_user(), 'multi_profiles')) {
$profile['edit'] = [System::baseUrl() . '/profiles/' . $profile['id'], L10n::t('Edit profile'), '', L10n::t('Edit profile')];
$profile['menu'] = [
'chg_photo' => L10n::t('Change profile photo'),
'cr_new' => null,
'entries' => [],
];
}
// Fetch the account type
$account_type = Contact::getAccountType($profile);
@ -525,10 +504,13 @@ class Profile
$o .= Renderer::replaceMacros($tpl, [
'$profile' => $p,
'$xmpp' => $xmpp,
'$connect' => $connect,
'$remoteconnect' => $remoteconnect,
'$subscribe_feed' => $subscribe_feed,
'$wallmessage' => $wallmessage,
'$follow' => L10n::t('Follow'),
'$follow_link' => $follow_link,
'$unfollow' => L10n::t('Unfollow'),
'$unfollow_link' => $unfollow_link,
'$subscribe_feed' => L10n::t('Atom feed'),
'$subscribe_feed_link' => $subscribe_feed_link,
'$wallmessage' => L10n::t('Message'),
'$wallmessage_link' => $wallmessage_link,
'$account_type' => $account_type,
'$location' => $location,
@ -877,32 +859,35 @@ class Profile
return '';
}
public static function getTabs($a, $is_owner = false, $nickname = null)
/**
* @param App $a
* @param string $current
* @param bool $is_owner
* @param string $nickname
* @return string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function getTabs(App $a, string $current, bool $is_owner, string $nickname = null)
{
if (is_null($nickname)) {
$nickname = $a->user['nickname'];
}
$tab = false;
if (!empty($_GET['tab'])) {
$tab = Strings::escapeTags(trim($_GET['tab']));
}
$url = System::baseUrl() . '/profile/' . $nickname;
$baseProfileUrl = System::baseUrl() . '/profile/' . $nickname;
$tabs = [
[
'label' => L10n::t('Status'),
'url' => $url,
'sel' => !$tab && $a->argv[0] == 'profile' ? 'active' : '',
'url' => $baseProfileUrl,
'sel' => !$current ? 'active' : '',
'title' => L10n::t('Status Messages and Posts'),
'id' => 'status-tab',
'accesskey' => 'm',
],
[
'label' => L10n::t('Profile'),
'url' => $url . '/?tab=profile',
'sel' => $tab == 'profile' ? 'active' : '',
'url' => $baseProfileUrl . '/?tab=profile',
'sel' => $current == 'profile' ? 'active' : '',
'title' => L10n::t('Profile Details'),
'id' => 'profile-tab',
'accesskey' => 'r',
@ -910,7 +895,7 @@ class Profile
[
'label' => L10n::t('Photos'),
'url' => System::baseUrl() . '/photos/' . $nickname,
'sel' => !$tab && $a->argv[0] == 'photos' ? 'active' : '',
'sel' => $current == 'photos' ? 'active' : '',
'title' => L10n::t('Photo Albums'),
'id' => 'photo-tab',
'accesskey' => 'h',
@ -918,7 +903,7 @@ class Profile
[
'label' => L10n::t('Videos'),
'url' => System::baseUrl() . '/videos/' . $nickname,
'sel' => !$tab && $a->argv[0] == 'videos' ? 'active' : '',
'sel' => $current == 'videos' ? 'active' : '',
'title' => L10n::t('Videos'),
'id' => 'video-tab',
'accesskey' => 'v',
@ -930,7 +915,7 @@ class Profile
$tabs[] = [
'label' => L10n::t('Events'),
'url' => System::baseUrl() . '/events',
'sel' => !$tab && $a->argv[0] == 'events' ? 'active' : '',
'sel' => $current == 'events' ? 'active' : '',
'title' => L10n::t('Events and Calendar'),
'id' => 'events-tab',
'accesskey' => 'e',
@ -941,7 +926,7 @@ class Profile
$tabs[] = [
'label' => L10n::t('Events'),
'url' => System::baseUrl() . '/cal/' . $nickname,
'sel' => !$tab && $a->argv[0] == 'cal' ? 'active' : '',
'sel' => $current == 'cal' ? 'active' : '',
'title' => L10n::t('Events and Calendar'),
'id' => 'events-tab',
'accesskey' => 'e',
@ -952,7 +937,7 @@ class Profile
$tabs[] = [
'label' => L10n::t('Personal Notes'),
'url' => System::baseUrl() . '/notes',
'sel' => !$tab && $a->argv[0] == 'notes' ? 'active' : '',
'sel' => $current == 'notes' ? 'active' : '',
'title' => L10n::t('Only You Can See This'),
'id' => 'notes-tab',
'accesskey' => 't',
@ -969,18 +954,18 @@ class Profile
];
}
if (!$is_owner && empty($a->profile['hide-friends'])) {
if ($is_owner || empty($a->profile['hide-friends'])) {
$tabs[] = [
'label' => L10n::t('Contacts'),
'url' => System::baseUrl() . '/viewcontacts/' . $nickname,
'sel' => !$tab && $a->argv[0] == 'viewcontacts' ? 'active' : '',
'url' => $baseProfileUrl . '/contacts',
'sel' => $current == 'contacts' ? 'active' : '',
'title' => L10n::t('Contacts'),
'id' => 'viewcontacts-tab',
'accesskey' => 'k',
];
}
$arr = ['is_owner' => $is_owner, 'nickname' => $nickname, 'tab' => $tab, 'tabs' => $tabs];
$arr = ['is_owner' => $is_owner, 'nickname' => $nickname, 'tab' => $current, 'tabs' => $tabs];
Hook::callAll('profile_tabs', $arr);
$tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
@ -995,10 +980,7 @@ class Profile
*/
public static function getMyURL()
{
if (!empty($_SESSION['my_url'])) {
return $_SESSION['my_url'];
}
return null;
return Session::get('my_url');
}
/**

View file

@ -16,11 +16,13 @@ use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\Model\Photo;
use Friendica\Object\Image;
use Friendica\Util\Crypto;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Network;
use Friendica\Util\Strings;
use Friendica\Worker\Delivery;
use LightOpenID;
/**
@ -148,10 +150,11 @@ class User
* @brief Get owner data by user id
*
* @param int $uid
* @param boolean $check_valid Test if data is invalid and correct it
* @return boolean|array
* @throws Exception
*/
public static function getOwnerDataById($uid) {
public static function getOwnerDataById($uid, $check_valid = true) {
$r = DBA::fetchFirst("SELECT
`contact`.*,
`user`.`prvkey` AS `uprvkey`,
@ -179,12 +182,34 @@ class User
return false;
}
// Check if the returned data is valid, otherwise fix it. See issue #6122
$url = System::baseUrl() . '/profile/' . $r['nickname'];
$addr = $r['nickname'] . '@' . substr(System::baseUrl(), strpos(System::baseUrl(), '://') + 3);
if (!$check_valid) {
return $r;
}
if (($addr != $r['addr']) || ($r['url'] != $url) || ($r['nurl'] != Strings::normaliseLink($r['url']))) {
// Check if the returned data is valid, otherwise fix it. See issue #6122
// Check for correct url and normalised nurl
$url = System::baseUrl() . '/profile/' . $r['nickname'];
$repair = ($r['url'] != $url) || ($r['nurl'] != Strings::normaliseLink($r['url']));
if (!$repair) {
// Check if "addr" is present and correct
$addr = $r['nickname'] . '@' . substr(System::baseUrl(), strpos(System::baseUrl(), '://') + 3);
$repair = ($addr != $r['addr']);
}
if (!$repair) {
// Check if the avatar field is filled and the photo directs to the correct path
$avatar = Photo::selectFirst(['resource-id'], ['uid' => $uid, 'profile' => true]);
if (DBA::isResult($avatar)) {
$repair = empty($r['avatar']) || !strpos($r['photo'], $avatar['resource-id']);
}
}
if ($repair) {
Contact::updateSelfFromUserID($uid);
// Return the corrected data and avoid a loop
$r = self::getOwnerDataById($uid, false);
}
return $r;
@ -912,7 +937,7 @@ class User
// 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('now + 7 day')], ['uid' => $uid]);
Worker::add(PRIORITY_HIGH, 'Notifier', 'removeme', $uid);
Worker::add(PRIORITY_HIGH, 'Notifier', Delivery::REMOVAL, $uid);
// Send an update to the directory
$self = DBA::selectFirst('contact', ['url'], ['uid' => $uid, 'self' => true]);

View file

@ -87,7 +87,9 @@ class Federation extends BaseAdminModule
$part = array_pop($parts);
} while (!empty($parts) && ((strlen($part) >= 40) || (strlen($part) <= 3)));
// only take the x.x.x part of the version, not the "release" after the dash
$part = array_shift(explode('-', $part));
if (!empty($part) && strpos($part, '-')) {
$part = explode('-', $part)[0];
}
if (!empty($part)) {
if (empty($compacted[$part])) {
$compacted[$part] = $versionCounts[$key]['total'];

View file

@ -22,6 +22,12 @@ class Settings extends BaseAdminModule
$debugging = !empty($_POST['debugging']);
$loglevel = defaults($_POST, 'loglevel', LogLevel::ERROR);
if (is_file($logfile) &&
!is_writeable($logfile)) {
notice(L10n::t('The logfile \'%s\' is not writable. No logging possible', $logfile));
return;
}
Config::set('system', 'logfile', $logfile);
Config::set('system', 'debugging', $debugging);
Config::set('system', 'loglevel', $loglevel);

View file

@ -16,6 +16,7 @@ use Friendica\Protocol\PortableContact;
use Friendica\Util\BasePath;
use Friendica\Util\BaseURL;
use Friendica\Util\Strings;
use Friendica\Worker\Delivery;
require_once __DIR__ . '/../../../boot.php';
@ -99,7 +100,7 @@ class Site extends BaseAdminModule
// send relocate
$usersStmt = DBA::select('user', ['uid'], ['account_removed' => false, 'account_expired' => false]);
while ($user = DBA::fetch($usersStmt)) {
Worker::add(PRIORITY_HIGH, 'Notifier', 'relocate', $user['uid']);
Worker::add(PRIORITY_HIGH, 'Notifier', Delivery::RELOCATION, $user['uid']);
}
info("Relocation started. Could take a while to complete.");

View file

@ -26,7 +26,7 @@ class Summary extends BaseAdminModule
// are there MyISAM tables in the DB? If so, trigger a warning message
$warningtext = [];
if (DBA::count('`information_schema`.`tables`', ['engine' => 'myisam', 'table_schema' => DBA::databaseName()])) {
if (DBA::count(['information_schema' => 'tables'], ['engine' => 'myisam', 'table_schema' => DBA::databaseName()])) {
$warningtext[] = L10n::t('Your DB still runs with MyISAM tables. You should change the engine type to InnoDB. As Friendica will use InnoDB only features in the future, you should change this! See <a href="%s">here</a> for a guide that may be helpful converting the table engines. You may also use the command <tt>php bin/console.php dbstructure toinnodb</tt> of your Friendica installation for an automatic conversion.<br />', 'https://dev.mysql.com/doc/refman/5.7/en/converting-tables-to-innodb.html');
}
@ -74,6 +74,23 @@ class Summary extends BaseAdminModule
$well_known, $well_known, $a->getBaseURL() . '/help/Install');
}
// Check logfile permission
if (Config::get('system', 'debugging')) {
$stream = Config::get('system', 'logfile');
if (is_file($stream) &&
!is_writeable($stream)) {
$warningtext[] = L10n::t('The logfile \'%s\' is not writable. No logging possible', $stream);
}
$stream = Config::get('system', 'dlogfile');
if (is_file($stream) &&
!is_writeable($stream)) {
$warningtext[] = L10n::t('The logfile \'%s\' is not writable. No logging possible', $stream);
}
}
// check legacy basepath settings
$configLoader = new ConfigFileLoader($a->getBasePath(), $a->getMode());
$configCache = new Config\Cache\ConfigCache();
@ -129,8 +146,6 @@ class Summary extends BaseAdminModule
$pending = Register::getPendingCount();
$queue = DBA::count('queue', []);
$deferred = DBA::count('workerqueue', ['`executed` <= ? AND NOT `done` AND `next_try` > ?',
DBA::NULL_DATETIME, DateTimeFormat::utcNow()]);
@ -138,7 +153,7 @@ class Summary extends BaseAdminModule
DBA::NULL_DATETIME, DateTimeFormat::utcNow()]);
// We can do better, but this is a quick queue status
$queues = ['label' => L10n::t('Message queues'), 'queue' => $queue, 'deferred' => $deferred, 'workerq' => $workerqueue];
$queues = ['label' => L10n::t('Message queues'), 'deferred' => $deferred, 'workerq' => $workerqueue];
$variables = DBA::toArray(DBA::p('SHOW variables LIKE "max_allowed_packet"'));
$max_allowed_packet = $variables ? $variables[0]['Value'] : 0;

View file

@ -6,7 +6,10 @@ use Friendica\BaseModule;
use Friendica\Core\Addon;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
use Friendica\Core\System;
use Friendica\Core\Session;
use Friendica\Network\HTTPException\ForbiddenException;
require_once 'boot.php';
/**
* This abstract module is meant to be extended by all modules that are reserved to administrator users.
@ -35,11 +38,11 @@ abstract class BaseAdminModule extends BaseModule
public static function rawContent()
{
if (!is_site_admin()) {
System::httpExit(403);
return '';
}
if (!empty($_SESSION['submanage'])) {
System::httpExit(403);
return '';
}
return '';
@ -47,21 +50,18 @@ abstract class BaseAdminModule extends BaseModule
public static function content()
{
$a = self::getApp();
if (!is_site_admin()) {
return Login::form();
notice(L10n::t('Please login to continue.'));
Session::set('return_path', $a->query_string);
$a->internalRedirect('login');
}
if (!empty($_SESSION['submanage'])) {
return '';
throw new ForbiddenException(L10n::t('Submanaged account can\'t access the administation pages. Please log back in as the master account.'));
}
$a = self::getApp();
// APC deactivated, since there are problems with PHP 5.5
//if (function_exists("apc_delete")) {
// $toDelete = new APCIterator('user', APC_ITER_VALUE);
// apc_delete($toDelete);
//}
// Header stuff
$a->page['htmlhead'] .= Renderer::replaceMacros(Renderer::getMarkupTemplate('admin/settings_head.tpl'), []);

View file

@ -0,0 +1,167 @@
<?php
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Content\ContactSelector;
use Friendica\Content\Pager;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
use Friendica\Core\Search;
use Friendica\Model;
use Friendica\Network\HTTPException;
use Friendica\Object\Search\ContactResult;
use Friendica\Object\Search\ResultList;
use Friendica\Util\Proxy as ProxyUtils;
use Friendica\Util\Strings;
/**
* Base class for search modules
*/
class BaseSearchModule extends BaseModule
{
/**
* Performs a search with an optional prefix
*
* @param string $prefix A optional prefix (e.g. @ or !) for searching
*
* @return string
* @throws HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function performSearch($prefix = '')
{
$a = self::getApp();
$config = $a->getConfig();
$type = Search::TYPE_ALL;
$localSearch = $config->get('system', 'poco_local_search');
$search = $prefix . Strings::escapeTags(trim(defaults($_REQUEST, 'search', '')));
if (!$search) {
return '';
}
$header = '';
if (strpos($search, '@') === 0) {
$search = substr($search, 1);
$type = Search::TYPE_PEOPLE;
$header = L10n::t('People Search - %s', $search);
if (strrpos($search, '@') > 0) {
$results = Search::getContactsFromProbe($search);
}
}
if (strpos($search, '!') === 0) {
$search = substr($search, 1);
$type = Search::TYPE_FORUM;
$header = L10n::t('Forum Search - %s', $search);
}
$pager = new Pager($a->query_string);
if ($localSearch && empty($results)) {
$pager->setItemsPerPage(80);
$results = Search::getContactsFromLocalDirectory($search, $type, $pager->getStart(), $pager->getItemsPerPage());
} elseif (strlen($config->get('system', 'directory')) && empty($results)) {
$results = Search::getContactsFromGlobalDirectory($search, $type, $pager->getPage());
$pager->setItemsPerPage($results->getItemsPage());
}
return self::printResult($results, $pager, $header);
}
/**
* Prints a human readable search result
*
* @param ResultList $results
* @param Pager $pager
* @param string $header
*
* @return string The result
* @throws HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
protected static function printResult(ResultList $results, Pager $pager, $header = '')
{
if ($results->getTotal() == 0) {
info(L10n::t('No matches'));
return '';
}
$a = self::getApp();
$id = 0;
$entries = [];
foreach ($results->getResults() as $result) {
// in case the result is a contact result, add a contact-specific entry
if ($result instanceof ContactResult) {
$alt_text = '';
$location = '';
$about = '';
$accountType = '';
$photo_menu = [];
// If We already know this contact then don't show the "connect" button
if ($result->getCid() > 0 || $result->getPCid() > 0) {
$connLink = "";
$connTxt = "";
$contact = Model\Contact::getById(
($result->getCid() > 0) ? $result->getCid() : $result->getPCid()
);
if (!empty($contact)) {
$photo_menu = Model\Contact::photoMenu($contact);
$details = Contact::getContactTemplateVars($contact);
$alt_text = $details['alt_text'];
$location = $contact['location'];
$about = $contact['about'];
$accountType = Model\Contact::getAccountType($contact);
} else {
$photo_menu = [];
}
} else {
$connLink = $a->getBaseURL() . '/follow/?url=' . $result->getUrl();
$connTxt = L10n::t('Connect');
$photo_menu['profile'] = [L10n::t("View Profile"), Model\Contact::magicLink($result->getUrl())];
$photo_menu['follow'] = [L10n::t("Connect/Follow"), $connLink];
}
$photo = str_replace("http:///photo/", get_server() . "/photo/", $result->getPhoto());
$entry = [
'alt_text' => $alt_text,
'url' => Model\Contact::magicLink($result->getUrl()),
'itemurl' => $result->getItem(),
'name' => $result->getName(),
'thumb' => ProxyUtils::proxifyUrl($photo, false, ProxyUtils::SIZE_THUMB),
'img_hover' => $result->getTags(),
'conntxt' => $connTxt,
'connlnk' => $connLink,
'photo_menu' => $photo_menu,
'details' => $location,
'tags' => $result->getTags(),
'about' => $about,
'account_type' => $accountType,
'network' => ContactSelector::networkToName($result->getNetwork(), $result->getUrl()),
'id' => ++$id,
];
$entries[] = $entry;
}
}
$tpl = Renderer::getMarkupTemplate('viewcontact_template.tpl');
return Renderer::replaceMacros($tpl, [
'title' => $header,
'$contacts' => $entries,
'$paginate' => $pager->renderFull($results->getTotal()),
]);
}
}

View file

@ -260,12 +260,13 @@ class Contact extends BaseModule
public static function content($update = 0)
{
if (!local_user()) {
return Login::form($_SERVER['REQUET_URI']);
return Login::form($_SERVER['REQUEST_URI']);
}
$a = self::getApp();
$nets = defaults($_GET, 'nets', '');
$rel = defaults($_GET, 'rel' , '');
if (empty($a->page['aside'])) {
$a->page['aside'] = '';
@ -308,6 +309,21 @@ class Contact extends BaseModule
$network_link = '';
}
$follow_link = '';
$unfollow_link = '';
if (in_array($contact['network'], Protocol::NATIVE_SUPPORT)) {
if ($contact['uid'] && in_array($contact['rel'], [Model\Contact::SHARING, Model\Contact::FRIEND])) {
$unfollow_link = 'unfollow?url=' . urlencode($contact['url']);
} elseif(!$contact['pending']) {
$follow_link = 'follow?url=' . urlencode($contact['url']);
}
}
$wallmessage_link = '';
if ($contact['uid'] && Model\Contact::canReceivePrivateMessages($contact)) {
$wallmessage_link = 'message/new/' . $contact['id'];
}
$vcard_widget = Renderer::replaceMacros(Renderer::getMarkupTemplate('widget/vcard.tpl'), [
'$name' => $contact['name'],
'$photo' => $contact['photo'],
@ -315,12 +331,19 @@ class Contact extends BaseModule
'$addr' => defaults($contact, 'addr', ''),
'$network_link' => $network_link,
'$network' => L10n::t('Network:'),
'$account_type' => Model\Contact::getAccountType($contact)
'$account_type' => Model\Contact::getAccountType($contact),
'$follow' => L10n::t('Follow'),
'$follow_link' => $follow_link,
'$unfollow' => L10n::t('Unfollow'),
'$unfollow_link' => $unfollow_link,
'$wallmessage' => L10n::t('Message'),
'$wallmessage_link' => $wallmessage_link,
]);
$findpeople_widget = '';
$follow_widget = '';
$networks_widget = '';
$rel_widget = '';
} else {
$vcard_widget = '';
$findpeople_widget = Widget::findPeople();
@ -331,6 +354,7 @@ class Contact extends BaseModule
}
$networks_widget = Widget::networks($_SERVER['REQUEST_URI'], $nets);
$rel_widget = Widget::contactRels($_SERVER['REQUEST_URI'], $rel);
}
if ($contact['uid'] != 0) {
@ -339,7 +363,7 @@ class Contact extends BaseModule
$groups_widget = null;
}
$a->page['aside'] .= $vcard_widget . $findpeople_widget . $follow_widget . $groups_widget . $networks_widget;
$a->page['aside'] .= $vcard_widget . $findpeople_widget . $follow_widget . $groups_widget . $networks_widget . $rel_widget;
$tpl = Renderer::getMarkupTemplate('contacts-head.tpl');
$a->page['htmlhead'] .= Renderer::replaceMacros($tpl, [
@ -554,19 +578,6 @@ class Contact extends BaseModule
$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 ($contact['uid'] && 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');
}
} elseif(!$contact['pending']) {
$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);
@ -607,8 +618,6 @@ class Contact extends BaseModule
'$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')),
@ -678,6 +687,7 @@ class Contact extends BaseModule
$search = Strings::escapeTags(trim(defaults($_GET, 'search', '')));
$nets = Strings::escapeTags(trim(defaults($_GET, 'nets' , '')));
$rel = Strings::escapeTags(trim(defaults($_GET, 'rel' , '')));
$tabs = [
[
@ -747,6 +757,12 @@ class Contact extends BaseModule
$sql_extra .= sprintf(" AND network = '%s' ", DBA::escape($nets));
}
switch ($rel) {
case 'followers': $sql_extra .= " AND `rel` IN (1, 3)"; break;
case 'following': $sql_extra .= " AND `rel` IN (2, 3)"; break;
case 'mutuals': $sql_extra .= " AND `rel` = 3"; break;
}
$sql_extra .= " AND NOT `deleted` ";
$sql_extra2 = ((($sort_type > 0) && ($sort_type <= Model\Contact::FRIEND)) ? sprintf(" AND `rel` = %d ", intval($sort_type)) : '');
@ -777,6 +793,13 @@ class Contact extends BaseModule
}
}
switch ($rel) {
case 'followers': $header = L10n::t('Followers'); break;
case 'following': $header = L10n::t('Following'); break;
case 'mutuals': $header = L10n::t('Mutual friends'); break;
default: $header = L10n::t('Contacts');
}
switch ($type) {
case 'blocked': $header .= ' - ' . L10n::t('Blocked'); break;
case 'hidden': $header .= ' - ' . L10n::t('Hidden'); break;
@ -927,10 +950,6 @@ class Contact extends BaseModule
$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);
}

View file

@ -41,6 +41,9 @@ class Fetch extends BaseModule
$item = Item::selectFirst(['author-link'], $condition);
if (empty($item)) {
$parts = parse_url($item["author-link"]);
if (empty($parts["scheme"]) || empty($parts["host"])) {
throw new HTTPException\InternalServerErrorException();
}
$host = $parts["scheme"] . "://" . $parts["host"];
if (Strings::normaliseLink($host) != Strings::normaliseLink($app->getBaseURL())) {

View file

@ -20,19 +20,6 @@ use Friendica\Util\Strings;
*/
class Directory extends BaseModule
{
public static function init()
{
$app = self::getApp();
if (local_user()) {
$app->page['aside'] .= Widget::findPeople();
$app->page['aside'] .= Widget::follow();
} else {
unset($_SESSION['theme']);
unset($_SESSION['mobile-theme']);
}
}
public static function content()
{
$app = self::getApp();
@ -43,6 +30,14 @@ class Directory extends BaseModule
throw new HTTPException\ForbiddenException(L10n::t('Public access denied.'));
}
if (local_user()) {
$app->page['aside'] .= Widget::findPeople();
$app->page['aside'] .= Widget::follow();
} else {
unset($_SESSION['theme']);
unset($_SESSION['mobile-theme']);
}
$output = '';
$entries = [];
@ -83,7 +78,7 @@ class Directory extends BaseModule
'$globaldir' => L10n::t('Global Directory'),
'$gDirPath' => $gDirPath,
'$desc' => L10n::t('Find on this site'),
'$contacts' => $profiles['entries'],
'$contacts' => $entries,
'$finding' => L10n::t('Results for:'),
'$findterm' => (strlen($search) ? $search : ""),
'$title' => L10n::t('Site Directory'),
@ -157,7 +152,7 @@ class Directory extends BaseModule
$entry = [
'id' => $contact['id'],
'url' => Contact::magicLInk($profile_link),
'url' => Contact::magicLink($profile_link),
'itemurl' => $itemurl,
'thumb' => ProxyUtils::proxifyUrl($contact[$photo_size], false, ProxyUtils::SIZE_THUMB),
'img_hover' => $contact['name'],

View file

@ -46,6 +46,6 @@ class RemoveTag extends BaseModule
info('Item was not deleted');
}
$app->internalRedirect('/network?f=&file=' . rawurlencode($term));
$app->internalRedirect('network?file=' . rawurlencode($term));
}
}

View file

@ -40,9 +40,8 @@ class SaveTag extends BaseModule
}
// return filer dialog
$filetags = PConfig::get(local_user(), 'system', 'filetags');
$filetags = Model\FileTag::fileToList($filetags, 'file');
$filetags = explode(",", $filetags);
$filetags = PConfig::get(local_user(), 'system', 'filetags', '');
$filetags = Model\FileTag::fileToArray($filetags);
$tpl = Renderer::getMarkupTemplate("filer_dialog.tpl");
echo Renderer::replaceMacros($tpl, [

View file

@ -1,34 +0,0 @@
<?php
namespace Friendica\Module\GnuSocial;
use Friendica\BaseModule;
use Friendica\Core\L10n;
use Friendica\Database\DBA;
use Friendica\Network\HTTPException;
/**
* GNU Social -> friendica items permanent-url compatibility
*/
class Notice extends BaseModule
{
public static function content()
{
$a = self::getApp();
// @TODO: Replace with parameter from router
$id = ($a->argc > 1) ? $a->argv[1] : 0;
if (empty($id)) {
throw new HTTPException\NotFoundException(L10n::t('Item not found.'));
}
$item = DBA::selectFirst('item', ['guid'], ['id' => $id]);
if (empty($item )) {
throw new HTTPException\NotFoundException(L10n::t('Item not found.'));
} else {
$a->internalRedirect('display/' . $item['guid']);
}
}
}

View file

@ -9,6 +9,8 @@ use Friendica\BaseModule;
use Friendica\Core\Authentication;
use Friendica\Core\Hook;
use Friendica\Core\L10n;
use Friendica\Core\System;
use Friendica\Model\Profile;
/**
* Logout module
@ -22,9 +24,19 @@ class Logout extends BaseModule
*/
public static function init()
{
$visitor_home = null;
if (remote_user()) {
$visitor_home = Profile::getMyURL();
}
Hook::callAll("logging_out");
Authentication::deleteSession();
info(L10n::t('Logged out.') . EOL);
self::getApp()->internalRedirect();
if ($visitor_home) {
System::externalRedirect($visitor_home);
} else {
info(L10n::t('Logged out.'));
self::getApp()->internalRedirect();
}
}
}

View file

@ -85,7 +85,7 @@ class Magic extends BaseModule
);
// Try to get an authentication token from the other instance.
$curlResult = Network::curl($basepath . '/owa', false, $redirects, ['headers' => $headers]);
$curlResult = Network::curl($basepath . '/owa', false, ['headers' => $headers]);
if ($curlResult->isSuccess()) {
$j = json_decode($curlResult->getBody(), true);

View file

@ -179,12 +179,9 @@ class Profile extends BaseModule
}
if (!$update) {
$tab = false;
if (!empty($_GET['tab'])) {
$tab = Strings::escapeTags(trim($_GET['tab']));
}
$tab = Strings::escapeTags(trim(defaults($_GET, 'tab', '')));
$o .= ProfileModel::getTabs($a, $is_owner, $a->profile['nickname']);
$o .= ProfileModel::getTabs($a, $tab, $is_owner, $a->profile['nickname']);
if ($tab === 'profile') {
$o .= ProfileModel::getAdvanced($a);
@ -197,8 +194,8 @@ class Profile extends BaseModule
$commpage = $a->profile['page-flags'] == User::PAGE_FLAGS_COMMUNITY;
$commvisitor = $commpage && $remote_contact;
$a->page['aside'] .= posted_date_widget(System::baseUrl(true) . '/profile/' . $a->profile['nickname'], $a->profile['profile_uid'], true);
$a->page['aside'] .= Widget::categories(System::baseUrl(true) . '/profile/' . $a->profile['nickname'], (!empty($category) ? XML::escape($category) : ''));
$a->page['aside'] .= Widget::postedByYear(System::baseUrl(true) . '/profile/' . $a->profile['nickname'], $a->profile['profile_uid'] ?? 0, true);
$a->page['aside'] .= Widget::categories(System::baseUrl(true) . '/profile/' . $a->profile['nickname'], XML::escape($category));
$a->page['aside'] .= Widget::tagCloud();
if (Security::canWriteToUserWall($a->profile['profile_uid'])) {

View file

@ -0,0 +1,136 @@
<?php
namespace Friendica\Module\Profile;
use Friendica\BaseModule;
use Friendica\Content\ContactSelector;
use Friendica\Content\Nav;
use Friendica\Content\Pager;
use Friendica\Core\Config;
use Friendica\Core\L10n;
use Friendica\Core\Protocol;
use Friendica\Core\Renderer;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Model\Profile;
use Friendica\Util\Proxy as ProxyUtils;
class Contacts extends BaseModule
{
public static function content()
{
if (Config::get('system', 'block_public') && !local_user() && !remote_user()) {
throw new \Friendica\Network\HTTPException\NotFoundException(L10n::t('User not found.'));
}
$a = self::getApp();
//@TODO: Get value from router parameters
$nickname = $a->argv[1];
$type = defaults($a->argv, 3, 'all');
Nav::setSelected('home');
$user = DBA::selectFirst('user', [], ['nickname' => $nickname, 'blocked' => false]);
if (!DBA::isResult($user)) {
throw new \Friendica\Network\HTTPException\NotFoundException(L10n::t('User not found.'));
}
$a->data['user'] = $user;
$a->profile_uid = $user['uid'];
Profile::load($a, $nickname);
$is_owner = $a->profile['profile_uid'] == local_user();
// tabs
$o = Profile::getTabs($a, 'contacts', $is_owner, $nickname);
if (!count($a->profile) || $a->profile['hide-friends']) {
notice(L10n::t('Permission denied.') . EOL);
return $o;
}
$condition = [
'uid' => $a->profile['uid'],
'blocked' => false,
'pending' => false,
'hidden' => false,
'archive' => false,
'network' => [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, Protocol::FEED]
];
switch ($type) {
case 'followers': $condition['rel'] = [1, 3]; break;
case 'following': $condition['rel'] = [2, 3]; break;
case 'mutuals': $condition['rel'] = 3; break;
}
$total = DBA::count('contact', $condition);
$pager = new Pager($a->query_string);
$params = ['order' => ['name' => false], 'limit' => [$pager->getStart(), $pager->getItemsPerPage()]];
$contacts_stmt = DBA::select('contact', [], $condition, $params);
if (!DBA::isResult($contacts_stmt)) {
info(L10n::t('No contacts.') . EOL);
return $o;
}
$contacts = [];
while ($contact = DBA::fetch($contacts_stmt)) {
if ($contact['self']) {
continue;
}
$contact_details = Contact::getDetailsByURL($contact['url'], $a->profile['uid'], $contact);
$contacts[] = [
'id' => $contact['id'],
'img_hover' => L10n::t('Visit %s\'s profile [%s]', $contact_details['name'], $contact['url']),
'photo_menu' => Contact::photoMenu($contact),
'thumb' => ProxyUtils::proxifyUrl($contact_details['thumb'], false, ProxyUtils::SIZE_THUMB),
'name' => substr($contact_details['name'], 0, 20),
'username' => $contact_details['name'],
'details' => $contact_details['location'],
'tags' => $contact_details['keywords'],
'about' => $contact_details['about'],
'account_type' => Contact::getAccountType($contact_details),
'url' => Contact::magicLink($contact['url']),
'sparkle' => '',
'itemurl' => $contact_details['addr'] ? : $contact['url'],
'network' => ContactSelector::networkToName($contact['network'], $contact['url']),
];
}
DBA::close($contacts_stmt);
switch ($type) {
case 'followers': $title = L10n::tt('Follower (%s)', 'Followers (%s)', $total); break;
case 'following': $title = L10n::tt('Following (%s)', 'Following (%s)', $total); break;
case 'mutuals': $title = L10n::tt('Mutual friend (%s)', 'Mutual friends (%s)', $total); break;
case 'all': default: $title = L10n::tt('Contact (%s)', 'Contacts (%s)', $total); break;
}
$tpl = Renderer::getMarkupTemplate('profile/contacts.tpl');
$o .= Renderer::replaceMacros($tpl, [
'$title' => $title,
'$nickname' => $nickname,
'$type' => $type,
'$all_label' => L10n::t('All contacts'),
'$followers_label' => L10n::t('Followers'),
'$following_label' => L10n::t('Following'),
'$mutuals_label' => L10n::t('Mutual friends'),
'$contacts' => $contacts,
'$paginate' => $pager->renderFull($total),
]);
return $o;
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Friendica\Module\Search;
use Friendica\Content\Widget;
use Friendica\Core\L10n;
use Friendica\Module\BaseSearchModule;
/**
* Directory search module
*/
class Directory extends BaseSearchModule
{
public static function content()
{
if (!local_user()) {
notice(L10n::t('Permission denied.'));
return Login::form();
}
$a = self::getApp();
if (empty($a->page['aside'])) {
$a->page['aside'] = '';
}
$a->page['aside'] .= Widget::findPeople();
$a->page['aside'] .= Widget::follow();
return self::performSearch();
}
}

69
src/Module/Welcome.php Normal file
View file

@ -0,0 +1,69 @@
<?php
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
/**
* Prints the welcome page for new users
*/
class Welcome extends BaseModule
{
public static function content()
{
$config = self::getApp()->getConfig();
$mail_disabled = ((function_exists('imap_open') &&
(!$config->get('system', 'imap_disabled'))));
$newuser_private = $config->get('system', 'newuser_private');
$tpl = Renderer::getMarkupTemplate('welcome.tpl');
return Renderer::replaceMacros($tpl, [
'$welcome' => L10n::t('Welcome to Friendica'),
'$checklist' => L10n::t('New Member Checklist'),
'$description' => L10n::t('We would like to offer some tips and links to help make your experience enjoyable. Click any item to visit the relevant page. A link to this page will be visible from your home page for two weeks after your initial registration and then will quietly disappear.'),
'$started' => L10n::t('Getting Started'),
'$quickstart_link' => L10n::t('Friendica Walk-Through'),
'$quickstart_txt' => L10n::t('On your <em>Quick Start</em> page - find a brief introduction to your profile and network tabs, make some new connections, and find some groups to join.'),
'$settings' => L10n::t('Settings'),
'$settings_link' => L10n::t('Go to Your Settings'),
'$settings_txt' => L10n::t('On your <em>Settings</em> page - change your initial password. Also make a note of your Identity Address. This looks just like an email address - and will be useful in making friends on the free social web.'),
'$settings_other' => L10n::t('Review the other settings, particularly the privacy settings. An unpublished directory listing is like having an unlisted phone number. In general, you should probably publish your listing - unless all of your friends and potential friends know exactly how to find you.'),
'$profile' => L10n::t('Profile'),
'$profile_photo_link' => L10n::t('Upload Profile Photo'),
'$profile_photo_txt' => L10n::t('Upload a profile photo if you have not done so already. Studies have shown that people with real photos of themselves are ten times more likely to make friends than people who do not.'),
'$profiles_link' => L10n::t('Edit Your Profile'),
'$profiles_txt' => L10n::t('Edit your <strong>default</strong> profile to your liking. Review the settings for hiding your list of friends and hiding the profile from unknown visitors.'),
'$profiles_keywords_link' => L10n::t('Profile Keywords'),
'$profiles_keywords_txt' => L10n::t('Set some public keywords for your default profile which describe your interests. We may be able to find other people with similar interests and suggest friendships.'),
'$connecting' => L10n::t('Connecting'),
'$mail_disabled' => $mail_disabled,
'$import_mail_link' => L10n::t('Importing Emails'),
'$import_mail_txt' => 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'),
'$contact_link' => L10n::t('Go to Your Contacts Page'),
'$contact_txt' => 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.'),
'$directory_link' => L10n::t('Go to Your Site\'s Directory'),
'$directory_txt' => 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.'),
'$finding_link' => L10n::t('Finding New People'),
'$finding_txt' => 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.'),
'$groups' => L10n::t('Groups'),
'$group_contact_link' => L10n::t('Group Your Contacts'),
'$group_contact_txt' => 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.'),
'$newuser_private' => $newuser_private,
'$private_link' => L10n::t('Why Aren\'t My Posts Public?'),
'$private_txt' => 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.'),
'$help' => L10n::t('Getting Help'),
'$help_link' => L10n::t('Go to the Help Section'),
'$help_txt' => L10n::t('Our <strong>help</strong> pages may be consulted for detail on other program features and resources.'),
]);
}
}

View file

@ -4,9 +4,11 @@ namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Core\Hook;
use Friendica\Database\DBA;
use Friendica\Core\Renderer;
use Friendica\Core\System;
use Friendica\Model\User;
use Friendica\Model\Photo;
use Friendica\Protocol\Salmon;
use Friendica\Util\Strings;
@ -61,67 +63,69 @@ class Xrd extends BaseModule
throw new \Friendica\Network\HTTPException\NotFoundException();
}
$profileURL = $app->getBaseURL() . '/profile/' . $user['nickname'];
$alias = str_replace('/profile/', '/~', $profileURL);
$owner = User::getOwnerDataById($user['uid']);
$addr = 'acct:' . $user['nickname'] . '@' . $app->getHostName();
if ($app->getURLPath()) {
$addr .= '/' . $app->getURLPath();
$alias = str_replace('/profile/', '/~', $owner['url']);
$avatar = Photo::selectFirst(['type'], ['uid' => $owner['uid'], 'profile' => true]);
if (!DBA::isResult($avatar)) {
$avatar = ['type' => 'image/jpeg'];
}
if ($mode == 'xml') {
self::printXML($addr, $alias, $profileURL, $app->getBaseURL(), $user);
self::printXML($alias, $app->getBaseURL(), $user, $owner, $avatar);
} else {
self::printJSON($addr, $alias, $profileURL, $app->getBaseURL(), $user);
self::printJSON($alias, $app->getBaseURL(), $owner, $avatar);
}
}
private static function printJSON($uri, $alias, $orofileURL, $baseURL, $user)
private static function printJSON($alias, $baseURL, $owner, $avatar)
{
$salmon_key = Salmon::salmonKey($user['spubkey']);
$salmon_key = Salmon::salmonKey($owner['spubkey']);
header('Access-Control-Allow-Origin: *');
header('Content-type: application/json; charset=utf-8');
$json = [
'subject' => $uri,
'subject' => 'acct:' . $owner['addr'],
'aliases' => [
$alias,
$orofileURL,
$owner['url'],
],
'links' => [
[
'rel' => NAMESPACE_DFRN,
'href' => $orofileURL,
'href' => $owner['url'],
],
[
'rel' => NAMESPACE_FEED,
'type' => 'application/atom+xml',
'href' => $baseURL . '/dfrn_poll/' . $user['nickname'],
'href' => $owner['poll'],
],
[
'rel' => 'http://webfinger.net/rel/profile-page',
'type' => 'text/html',
'href' => $orofileURL,
'href' => $owner['url'],
],
[
'rel' => 'self',
'type' => 'application/activity+json',
'href' => $orofileURL,
'href' => $owner['url'],
],
[
'rel' => 'http://microformats.org/profile/hcard',
'type' => 'text/html',
'href' => $baseURL . '/hcard/' . $user['nickname'],
'href' => $baseURL . '/hcard/' . $owner['nickname'],
],
[
'rel' => NAMESPACE_POCO,
'href' => $baseURL . '/poco/' . $user['nickname'],
'href' => $owner['poco'],
],
[
'rel' => 'http://webfinger.net/rel/avatar',
'type' => 'image/jpeg',
'href' => $baseURL . '/photo/profile/' . $user['uid'] . '.jpg',
'type' => $avatar['type'],
'href' => $owner['photo'],
],
[
'rel' => 'http://joindiaspora.com/seed_location',
@ -130,15 +134,15 @@ class Xrd extends BaseModule
],
[
'rel' => 'salmon',
'href' => $baseURL . '/salmon/' . $user['nickname'],
'href' => $baseURL . '/salmon/' . $owner['nickname'],
],
[
'rel' => 'http://salmon-protocol.org/ns/salmon-replies',
'href' => $baseURL . '/salmon/' . $user['nickname'],
'href' => $baseURL . '/salmon/' . $owner['nickname'],
],
[
'rel' => 'http://salmon-protocol.org/ns/salmon-mention',
'href' => $baseURL . '/salmon/' . $user['nickname'] . '/mention',
'href' => $baseURL . '/salmon/' . $owner['nickname'] . '/mention',
],
[
'rel' => 'http://ostatus.org/schema/1.0/subscribe',
@ -160,9 +164,9 @@ class Xrd extends BaseModule
exit();
}
private static function printXML($uri, $alias, $profileURL, $baseURL, $user)
private static function printXML($alias, $baseURL, $user, $owner, $avatar)
{
$salmon_key = Salmon::salmonKey($user['spubkey']);
$salmon_key = Salmon::salmonKey($owner['spubkey']);
header('Access-Control-Allow-Origin: *');
header('Content-type: text/xml');
@ -170,16 +174,17 @@ class Xrd extends BaseModule
$tpl = Renderer::getMarkupTemplate('xrd_person.tpl');
$o = Renderer::replaceMacros($tpl, [
'$nick' => $user['nickname'],
'$accturi' => $uri,
'$nick' => $owner['nickname'],
'$accturi' => 'acct:' . $owner['addr'],
'$alias' => $alias,
'$profile_url' => $profileURL,
'$hcard_url' => $baseURL . '/hcard/' . $user['nickname'],
'$atom' => $baseURL . '/dfrn_poll/' . $user['nickname'],
'$poco_url' => $baseURL . '/poco/' . $user['nickname'],
'$photo' => $baseURL . '/photo/profile/' . $user['uid'] . '.jpg',
'$salmon' => $baseURL . '/salmon/' . $user['nickname'],
'$salmen' => $baseURL . '/salmon/' . $user['nickname'] . '/mention',
'$profile_url' => $owner['url'],
'$hcard_url' => $baseURL . '/hcard/' . $owner['nickname'],
'$atom' => $owner['poll'],
'$poco_url' => $owner['poco'],
'$photo' => $owner['photo'],
'$type' => $avatar['type'],
'$salmon' => $baseURL . '/salmon/' . $owner['nickname'],
'$salmen' => $baseURL . '/salmon/' . $owner['nickname'] . '/mention',
'$subscribe' => $baseURL . '/follow?url={uri}',
'$openwebauth' => $baseURL . '/owa',
'$modexp' => 'data:application/magic-public-key,' . $salmon_key

View file

@ -109,12 +109,11 @@ class Probe
$url = "http://".$host."/.well-known/host-meta";
$xrd_timeout = Config::get('system', 'xrd_timeout', 20);
$redirects = 0;
Logger::log("Probing for ".$host, Logger::DEBUG);
$xrd = null;
$curlResult = Network::curl($ssl_url, false, $redirects, ['timeout' => $xrd_timeout, 'accept_content' => 'application/xrd+xml']);
$curlResult = Network::curl($ssl_url, false, ['timeout' => $xrd_timeout, 'accept_content' => 'application/xrd+xml']);
if ($curlResult->isSuccess()) {
$xml = $curlResult->getBody();
$xrd = XML::parseString($xml, false);
@ -122,7 +121,7 @@ class Probe
}
if (!is_object($xrd)) {
$curlResult = Network::curl($url, false, $redirects, ['timeout' => $xrd_timeout, 'accept_content' => 'application/xrd+xml']);
$curlResult = Network::curl($url, false, ['timeout' => $xrd_timeout, 'accept_content' => 'application/xrd+xml']);
if ($curlResult->isTimeout()) {
Logger::log("Probing timeout for " . $url, Logger::DEBUG);
self::$istimeout = true;
@ -199,7 +198,7 @@ class Probe
$links = self::lrdd($webbie);
Logger::log('webfingerDfrn: '.$webbie.':'.print_r($links, true), Logger::DATA);
if (count($links)) {
if (!empty($links) && is_array($links)) {
foreach ($links as $link) {
if ($link['@attributes']['rel'] === NAMESPACE_DFRN) {
$profile_link = $link['@attributes']['href'];
@ -738,9 +737,8 @@ class Probe
private static function webfinger($url, $type)
{
$xrd_timeout = Config::get('system', 'xrd_timeout', 20);
$redirects = 0;
$curlResult = Network::curl($url, false, $redirects, ['timeout' => $xrd_timeout, 'accept_content' => $type]);
$curlResult = Network::curl($url, false, ['timeout' => $xrd_timeout, 'accept_content' => $type]);
if ($curlResult->isTimeout()) {
self::$istimeout = true;
return false;

View file

@ -781,7 +781,7 @@ class Image
$data = Cache::get($url);
if (is_null($data) || !$data || !is_array($data)) {
$img_str = Network::fetchUrl($url, true, $redirects, 4);
$img_str = Network::fetchUrl($url, true, 4);
if (!$img_str) {
return false;

View file

@ -0,0 +1,147 @@
<?php
namespace Friendica\Object\Search;
use Friendica\Model\Search;
/**
* A search result for contact searching
*
* @see Search for details
*/
class ContactResult implements IResult
{
/**
* @var int
*/
private $cid;
/**
* @var int
*/
private $pCid;
/**
* @var string
*/
private $name;
/**
* @var string
*/
private $addr;
/**
* @var string
*/
private $item;
/**
* @var string
*/
private $url;
/**
* @var string
*/
private $photo;
/**
* @var string
*/
private $tags;
/**
* @var string
*/
private $network;
/**
* @return int
*/
public function getCid()
{
return $this->cid;
}
/**
* @return int
*/
public function getPCid()
{
return $this->pCid;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @return string
*/
public function getAddr()
{
return $this->addr;
}
/**
* @return string
*/
public function getItem()
{
return $this->item;
}
/**
* @return string
*/
public function getUrl()
{
return $this->url;
}
/**
* @return string
*/
public function getPhoto()
{
return $this->photo;
}
/**
* @return string
*/
public function getTags()
{
return $this->tags;
}
/**
* @return string
*/
public function getNetwork()
{
return $this->network;
}
/**
* @param string $name
* @param string $addr
* @param string $item
* @param string $url
* @param string $photo
* @param string $network
* @param int $cid
* @param int $pCid
* @param string $tags
*/
public function __construct($name, $addr, $item, $url, $photo, $network, $cid = 0, $pCid = 0, $tags = '')
{
$this->name = $name;
$this->addr = $addr;
$this->item = $item;
$this->url = $url;
$this->photo = $photo;
$this->network = $network;
$this->cid = $cid;
$this->pCid = $pCid;
$this->tags = $tags;
}
}

View file

@ -0,0 +1,11 @@
<?php
namespace Friendica\Object\Search;
/**
* The default interface for each search result
*/
interface IResult
{
}

View file

@ -0,0 +1,92 @@
<?php
namespace Friendica\Object\Search;
use Friendica\Model\Search;
/**
* A list of search results with metadata
*
* @see Search for details
*/
class ResultList
{
/**
* Page of the result list
* @var int
*/
private $page;
/**
* Total count of results
* @var int
*/
private $total;
/**
* items per page
* @var int
*/
private $itemsPage;
/**
* Array of results
*
* @var IResult[]
*/
private $results;
/**
* @return int
*/
public function getPage()
{
return $this->page;
}
/**
* @return int
*/
public function getTotal()
{
return $this->total;
}
/**
* @return int
*/
public function getItemsPage()
{
return $this->itemsPage;
}
/**
* @return IResult[]
*/
public function getResults()
{
return $this->results;
}
/**
* @param int $page
* @param int $total
* @param int $itemsPage
* @param IResult[] $results
*/
public function __construct($page = 0, $total = 0, $itemsPage = 0, array $results = [])
{
$this->page = $page;
$this->total = $total;
$this->itemsPage = $itemsPage;
$this->results = $results;
}
/**
* Adds a result to the result list
*
* @param IResult $result
*/
public function addResult(IResult $result)
{
$this->results[] = $result;
}
}

View file

@ -74,7 +74,7 @@ class ActivityPub
return HTTPSignature::fetch($url, $uid);
}
$curlResult = Network::curl($url, false, $redirects, ['accept_content' => 'application/activity+json, application/ld+json']);
$curlResult = Network::curl($url, false, ['accept_content' => 'application/activity+json, application/ld+json']);
if (!$curlResult->isSuccess() || empty($curlResult->getBody())) {
return false;
}

View file

@ -142,7 +142,7 @@ class Processor
}
$item['changed'] = DateTimeFormat::utcNow();
$item['edited'] = $activity['updated'];
$item['edited'] = DateTimeFormat::utc($activity['updated']);
$item = self::processContent($activity, $item);
if (empty($item)) {
@ -198,6 +198,43 @@ class Processor
Item::delete(['uri' => $activity['object_id'], 'owner-id' => $owner]);
}
/**
* Prepare the item array for an activity
*
* @param array $activity Activity array
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function addTag($activity)
{
if (empty($activity['object_content']) || empty($activity['object_id'])) {
return;
}
foreach ($activity['receiver'] as $receiver) {
$item = Item::selectFirst(['id', 'tag', 'origin', 'author-link'], ['uri' => $activity['target_id'], 'uid' => $receiver]);
if (!DBA::isResult($item)) {
// We don't fetch missing content for this purpose
continue;
}
if (($item['author-link'] != $activity['actor']) && !$item['origin']) {
Logger::info('Not origin, not from the author, skipping update', ['id' => $item['id'], 'author' => $item['author-link'], 'actor' => $activity['actor']]);
continue;
}
// To-Do:
// - Check if "blocktag" is set
// - Check if actor is a contact
if (!stristr($item['tag'], trim($activity['object_content']))) {
$tag = $item['tag'] . (strlen($item['tag']) ? ',' : '') . '#[url=' . $activity['object_id'] . ']'. $activity['object_content'] . '[/url]';
Item::update(['tag' => $tag], ['id' => $item['id']]);
Logger::info('Tagged item', ['id' => $item['id'], 'tag' => $activity['object_content'], 'uri' => $activity['target_id'], 'actor' => $activity['actor']]);
}
}
}
/**
* Prepare the item array for an activity
*
@ -333,20 +370,23 @@ class Processor
$item['private'] = !in_array(0, $activity['receiver']);
$item['author-link'] = $activity['author'];
$item['author-id'] = Contact::getIdForURL($activity['author'], 0, true);
$item['owner-link'] = $activity['actor'];
$item['owner-id'] = Contact::getIdForURL($activity['actor'], 0, true);
if (empty($activity['thread-completion'])) {
$item['owner-link'] = $activity['actor'];
$item['owner-id'] = Contact::getIdForURL($activity['actor'], 0, true);
} else {
Logger::info('Ignoring actor because of thread completion.');
if (!empty($activity['thread-completion'])) {
// Store the original actor in the "causer" fields to enable the check for ignored or blocked contacts
$item['causer-link'] = $item['owner-link'];
$item['causer-id'] = $item['owner-id'];
Logger::info('Ignoring actor because of thread completion.', ['actor' => $item['owner-link']]);
$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['created'] = DateTimeFormat::utc($activity['published']);
$item['edited'] = DateTimeFormat::utc($activity['updated']);
$item['guid'] = $activity['diaspora:guid'];
$item = self::processContent($activity, $item);
@ -405,8 +445,8 @@ class Processor
*
* @param array $activity Activity data
* @param array $item item array
* @return int|bool New mail table row id or false on error
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
private static function postMail($activity, $item)
{
@ -460,7 +500,7 @@ class Processor
}
$msg['body'] = $item['body'];
Mail::insert($msg);
return Mail::insert($msg);
}
/**
@ -530,7 +570,7 @@ class Processor
DBA::update('contact', ['hub-verify' => $activity['id'], 'protocol' => Protocol::ACTIVITYPUB], ['id' => $cid]);
$contact = DBA::selectFirst('contact', [], ['id' => $cid, 'network' => Protocol::NATIVE_SUPPORT]);
} else {
$contact = false;
$contact = [];
}
$item = ['author-id' => Contact::getIdForURL($activity['actor']),
@ -541,7 +581,11 @@ class Processor
// Ensure that the contact has got the right network type
self::switchContact($item['author-id']);
Contact::addRelationship($owner, $contact, $item, '', false, $note);
$result = Contact::addRelationship($owner, $contact, $item, false, $note);
if ($result === true) {
ActivityPub\Transmitter::sendContactAccept($item['author-link'], $item['author-id'], $owner['uid']);
}
$cid = Contact::getIdForURL($activity['actor'], $uid);
if (empty($cid)) {
return;

View file

@ -194,6 +194,11 @@ class Receiver
return [];
}
if (!is_string($object_id)) {
Logger::info('Invalid object id', ['object' => $object_id]);
return [];
}
$object_type = self::fetchObjectType($activity, $object_id, $uid);
// Fetch the content only on activities where this matters
@ -217,8 +222,7 @@ class Receiver
// 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']) ||
(($type == 'as:Follow') && in_array($object_type, self::CONTENT_TYPES))) {
} elseif (in_array($type, array_merge(self::ACTIVITY_TYPES, ['as:Follow'])) && in_array($object_type, self::CONTENT_TYPES)) {
// 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);
@ -226,6 +230,13 @@ class Receiver
$object_data['author'] = JsonLD::fetchElement($activity, 'as:actor', '@id');
$object_data['object_id'] = $object_id;
$object_data['object_type'] = ''; // Since we don't fetch the object, we don't know the type
} elseif (in_array($type, ['as:Add'])) {
$object_data = [];
$object_data['id'] = JsonLD::fetchElement($activity, '@id');
$object_data['target_id'] = JsonLD::fetchElement($activity, 'as:target', '@id');
$object_data['object_id'] = JsonLD::fetchElement($activity, 'as:object', '@id');
$object_data['object_type'] = JsonLD::fetchElement($activity['as:object'], '@type');
$object_data['object_content'] = JsonLD::fetchElement($activity['as:object'], 'as:content', '@type');
} else {
$object_data = [];
$object_data['id'] = JsonLD::fetchElement($activity, '@id');
@ -366,6 +377,12 @@ class Receiver
}
break;
case 'as:Add':
if ($object_data['object_type'] == 'as:tag') {
ActivityPub\Processor::addTag($object_data);
}
break;
case 'as:Announce':
if (in_array($object_data['object_type'], self::CONTENT_TYPES)) {
$profile = APContact::getByURL($object_data['actor']);
@ -723,11 +740,11 @@ class Receiver
* @param boolean $trust_source Do we trust the provided object?
* @param integer $uid User ID for the signature that we use to fetch data
*
* @return array with trusted and valid object data
* @return array|false with trusted and valid object data
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
private static function fetchObject($object_id, $object = [], $trust_source = false, $uid = 0)
private static function fetchObject(string $object_id, array $object = [], bool $trust_source = false, int $uid = 0)
{
// By fetching the type we check if the object is complete.
$type = JsonLD::fetchElement($object, '@type');
@ -766,13 +783,14 @@ class Receiver
if ($type == 'as:Announce') {
$object_id = JsonLD::fetchElement($object, 'object', '@id');
if (empty($object_id)) {
if (empty($object_id) || !is_string($object_id)) {
return false;
}
return self::fetchObject($object_id, [], false, $uid);
}
Logger::log('Unhandled object type: ' . $type, Logger::DEBUG);
return false;
}
/**

View file

@ -21,6 +21,7 @@ use Friendica\Model\User;
use Friendica\Util\DateTimeFormat;
use Friendica\Content\Text\BBCode;
use Friendica\Content\Text\Plaintext;
use Friendica\Util\XML;
use Friendica\Util\JsonLD;
use Friendica\Util\LDSignature;
use Friendica\Model\Profile;
@ -228,11 +229,6 @@ class Transmitter
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'];
@ -253,7 +249,7 @@ class Transmitter
'publicKeyPem' => $user['pubkey']];
$data['endpoints'] = ['sharedInbox' => System::baseUrl() . '/inbox'];
$data['icon'] = ['type' => 'Image',
'url' => $contact['avatar']];
'url' => $contact['photo']];
$data['generator'] = self::getService();
@ -435,7 +431,7 @@ class Transmitter
$data['to'][] = $profile['url'];
} else {
$data['cc'][] = $profile['url'];
if (!$item['private']) {
if (!$item['private'] && !empty($actor_profile['followers'])) {
$data['cc'][] = $actor_profile['followers'];
}
}
@ -772,6 +768,8 @@ class Transmitter
$type = 'TentativeAccept';
} elseif ($item['verb'] == ACTIVITY_FOLLOW) {
$type = 'Follow';
} elseif ($item['verb'] == ACTIVITY_TAG) {
$type = 'Add';
} else {
$type = '';
}
@ -875,6 +873,8 @@ class Transmitter
if (in_array($data['type'], ['Create', 'Update', 'Delete'])) {
$data['object'] = self::createNote($item);
} elseif ($data['type'] == 'Add') {
$data = self::createAddTag($item, $data);
} elseif ($data['type'] == 'Announce') {
$data = self::createAnnounce($item, $data);
} elseif ($data['type'] == 'Follow') {
@ -1136,7 +1136,7 @@ class Transmitter
{
$event = [];
$event['name'] = $item['event-summary'];
$event['content'] = BBCode::convert($item['event-desc'], false, 7);
$event['content'] = BBCode::convert($item['event-desc'], false, 9);
$event['startTime'] = DateTimeFormat::utc($item['event-start'] . '+00:00', DateTimeFormat::ATOM);
if (!$item['event-nofinish']) {
@ -1226,7 +1226,7 @@ class Transmitter
$regexp = "/[@!]\[url\=([^\[\]]*)\].*?\[\/url\]/ism";
$body = preg_replace_callback($regexp, ['self', 'mentionCallback'], $body);
$data['content'] = BBCode::convert($body, false, 7);
$data['content'] = BBCode::convert($body, false, 9);
}
$data['source'] = ['content' => $item['body'], 'mediaType' => "text/bbcode"];
@ -1251,6 +1251,30 @@ class Transmitter
return $data;
}
/**
* Creates an an "add tag" entry
*
* @param array $item
* @param array $data activity data
*
* @return array with activity data for adding tags
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
private static function createAddTag($item, $data)
{
$object = XML::parseString($item['object'], false);
$target = XML::parseString($item["target"], false);
$data['diaspora:guid'] = $item['guid'];
$data['actor'] = $item['author-link'];
$data['target'] = (string)$target->id;
$data['summary'] = BBCode::toPlaintext($item['body']);
$data['object'] = ['id' => (string)$object->id, 'type' => 'tag', 'name' => (string)$object->title, 'content' => (string)$object->content];
return $data;
}
/**
* Creates an announce object entry
*
@ -1450,6 +1474,10 @@ class Transmitter
public static function sendActivity($activity, $target, $uid, $id = '')
{
$profile = APContact::getByURL($target);
if (empty($profile['inbox'])) {
Logger::warning('No inbox found for target', ['target' => $target, 'profile' => $profile]);
return;
}
$owner = User::getOwnerDataById($uid);
@ -1486,6 +1514,10 @@ class Transmitter
public static function sendFollowObject($object, $target, $uid = 0)
{
$profile = APContact::getByURL($target);
if (empty($profile['inbox'])) {
Logger::warning('No inbox found for target', ['target' => $target, 'profile' => $profile]);
return;
}
if (empty($uid)) {
// Fetch the list of administrators
@ -1532,19 +1564,26 @@ class Transmitter
public static function sendContactAccept($target, $id, $uid)
{
$profile = APContact::getByURL($target);
if (empty($profile['inbox'])) {
Logger::warning('No inbox found for target', ['target' => $target, 'profile' => $profile]);
return;
}
$owner = User::getOwnerDataById($uid);
$data = ['@context' => ActivityPub::CONTEXT,
'id' => System::baseUrl() . '/activity/' . System::createGUID(),
'type' => 'Accept',
'actor' => $owner['url'],
'object' => ['id' => $id, 'type' => 'Follow',
'object' => [
'id' => (string)$id,
'type' => 'Follow',
'actor' => $profile['url'],
'object' => $owner['url']],
'object' => $owner['url']
],
'instrument' => self::getService(),
'to' => [$profile['url']]];
Logger::log('Sending accept to ' . $target . ' for user ' . $uid . ' with id ' . $id, Logger::DEBUG);
Logger::debug('Sending accept to ' . $target . ' for user ' . $uid . ' with id ' . $id);
$signed = LDSignature::sign($data, $owner);
HTTPSignature::transmit($signed, $profile['inbox'], $uid);
@ -1562,19 +1601,26 @@ class Transmitter
public static function sendContactReject($target, $id, $uid)
{
$profile = APContact::getByURL($target);
if (empty($profile['inbox'])) {
Logger::warning('No inbox found for target', ['target' => $target, 'profile' => $profile]);
return;
}
$owner = User::getOwnerDataById($uid);
$data = ['@context' => ActivityPub::CONTEXT,
'id' => System::baseUrl() . '/activity/' . System::createGUID(),
'type' => 'Reject',
'actor' => $owner['url'],
'object' => ['id' => $id, 'type' => 'Follow',
'object' => [
'id' => (string)$id,
'type' => 'Follow',
'actor' => $profile['url'],
'object' => $owner['url']],
'object' => $owner['url']
],
'instrument' => self::getService(),
'to' => [$profile['url']]];
Logger::log('Sending reject to ' . $target . ' for user ' . $uid . ' with id ' . $id, Logger::DEBUG);
Logger::debug('Sending reject to ' . $target . ' for user ' . $uid . ' with id ' . $id);
$signed = LDSignature::sign($data, $owner);
HTTPSignature::transmit($signed, $profile['inbox'], $uid);
@ -1592,6 +1638,10 @@ class Transmitter
public static function sendContactUndo($target, $cid, $uid)
{
$profile = APContact::getByURL($target);
if (empty($profile['inbox'])) {
Logger::warning('No inbox found for target', ['target' => $target, 'profile' => $profile]);
return;
}
$object_id = self::activityIDFromContact($cid);
if (empty($object_id)) {

View file

@ -1229,7 +1229,6 @@ class DFRN
$curlResult = Network::curl($url);
if ($curlResult->isTimeout()) {
Contact::markForArchival($contact);
return -2; // timed out
}
@ -1237,29 +1236,24 @@ class DFRN
$curl_stat = $curlResult->getReturnCode();
if (empty($curl_stat)) {
Contact::markForArchival($contact);
return -3; // timed out
}
Logger::log('dfrn_deliver: ' . $xml, Logger::DATA);
if (empty($xml)) {
Contact::markForArchival($contact);
return 3;
}
if (strpos($xml, '<?xml') === false) {
Logger::log('dfrn_deliver: no valid XML returned');
Logger::log('dfrn_deliver: returned XML: ' . $xml, Logger::DATA);
Contact::markForArchival($contact);
return 3;
}
$res = XML::parseString($xml);
if (!is_object($res) || (intval($res->status) != 0) || !strlen($res->challenge) || !strlen($res->dfrn_id)) {
Contact::markForArchival($contact);
if (empty($res->status)) {
$status = 3;
} else {
@ -1315,7 +1309,6 @@ class DFRN
if ($final_dfrn_id != $orig_id) {
Logger::log('dfrn_deliver: wrong dfrn_id.');
// did not decode properly - cannot trust this site
Contact::markForArchival($contact);
return 3;
}
@ -1351,7 +1344,6 @@ class DFRN
default:
Logger::log("rino: invalid requested version '$rino_remote_version'");
Contact::markForArchival($contact);
return -8;
}
@ -1391,26 +1383,22 @@ class DFRN
$curl_stat = $postResult->getReturnCode();
if (empty($curl_stat) || empty($xml)) {
Contact::markForArchival($contact);
return -9; // timed out
}
if (($curl_stat == 503) && stristr($postResult->getHeader(), 'retry-after')) {
Contact::markForArchival($contact);
return -10;
}
if (strpos($xml, '<?xml') === false) {
Logger::log('dfrn_deliver: phase 2: no valid XML returned');
Logger::log('dfrn_deliver: phase 2: returned XML: ' . $xml, Logger::DATA);
Contact::markForArchival($contact);
return 3;
}
$res = XML::parseString($xml);
if (!isset($res->status)) {
Contact::markForArchival($contact);
return -11;
}
@ -1423,10 +1411,6 @@ class DFRN
Logger::log('Delivery returned status '.$res->status.' - '.$res->message, Logger::DEBUG);
}
if (($res->status >= 200) && ($res->status <= 299)) {
Contact::unmarkForArchival($contact);
}
return intval($res->status);
}
@ -1454,7 +1438,6 @@ class DFRN
if (empty($contact['addr'])) {
Logger::log('Unable to find contact handle for ' . $contact['id'] . ' - ' . $contact['url']);
Contact::markForArchival($contact);
return -21;
}
}
@ -1462,7 +1445,6 @@ class DFRN
$fcontact = Diaspora::personByHandle($contact['addr']);
if (empty($fcontact)) {
Logger::log('Unable to find contact details for ' . $contact['id'] . ' - ' . $contact['addr']);
Contact::markForArchival($contact);
return -22;
}
$pubkey = $fcontact['pubkey'];
@ -1491,26 +1473,22 @@ class DFRN
$curl_stat = $postResult->getReturnCode();
if (empty($curl_stat) || empty($xml)) {
Logger::log('Empty answer from ' . $contact['id'] . ' - ' . $dest_url);
Contact::markForArchival($contact);
return -9; // timed out
}
if (($curl_stat == 503) && (stristr($postResult->getHeader(), 'retry-after'))) {
Contact::markForArchival($contact);
return -10;
}
if (strpos($xml, '<?xml') === false) {
Logger::log('No valid XML returned from ' . $contact['id'] . ' - ' . $dest_url);
Logger::log('Returned XML: ' . $xml, Logger::DATA);
Contact::markForArchival($contact);
return 3;
}
$res = XML::parseString($xml);
if (empty($res->status)) {
Contact::markForArchival($contact);
return -23;
}
@ -1518,10 +1496,6 @@ class DFRN
Logger::log('Transmit to ' . $dest_url . ' returned status '.$res->status.' - '.$res->message, Logger::DEBUG);
}
if (($res->status >= 200) && ($res->status <= 299)) {
Contact::unmarkForArchival($contact);
}
return intval($res->status);
}
@ -2242,18 +2216,16 @@ class DFRN
{
Logger::log("Process verb ".$item["verb"]." and object-type ".$item["object-type"]." for entrytype ".$entrytype, Logger::DEBUG);
if (($entrytype == DFRN::TOP_LEVEL)) {
if (($entrytype == DFRN::TOP_LEVEL) && !empty($importer['id'])) {
// The filling of the the "contact" variable is done for legcy reasons
// The functions below are partly used by ostatus.php as well - where we have this variable
$r = q("SELECT * FROM `contact` WHERE `id` = %d", intval($importer["id"]));
$contact = $r[0];
$nickname = $contact["nick"];
$contact = Contact::select([], ['id' => $importer['id']]);
// Big question: Do we need these functions? They were part of the "consume_feed" function.
// This function once was responsible for DFRN and OStatus.
if (activity_match($item["verb"], ACTIVITY_FOLLOW)) {
Logger::log("New follower");
Contact::addRelationship($importer, $contact, $item, $nickname);
Contact::addRelationship($importer, $contact, $item);
return false;
}
if (activity_match($item["verb"], ACTIVITY_UNFOLLOW)) {
@ -2263,7 +2235,7 @@ class DFRN
}
if (activity_match($item["verb"], ACTIVITY_REQ_FRIEND)) {
Logger::log("New friend request");
Contact::addRelationship($importer, $contact, $item, $nickname, true);
Contact::addRelationship($importer, $contact, $item, true);
return false;
}
if (activity_match($item["verb"], ACTIVITY_UNFRIEND)) {
@ -2927,7 +2899,12 @@ class DFRN
{
// prevent looping
if (!empty($_REQUEST['redir'])) {
return;
Logger::log('autoRedir might be looping because redirect has been redirected', Logger::DEBUG);
// looping prevention also appears to sometimes prevent authentication for images
// because browser may have multiple connections open and load an image on a connection
// whose session wasn't updated when a previous redirect authenticated
// Leaving commented in case looping reappears
//return;
}
if ((! $contact_nick) || ($contact_nick === $a->user['nickname'])) {
@ -2951,6 +2928,9 @@ class DFRN
$baseurl = substr($baseurl, $domain_st + 3);
$nurl = Strings::normaliseLink($baseurl);
$r = User::getByNickname($contact_nick, ["uid"]);
$contact_uid = $r["uid"];
/// @todo Why is there a query for "url" *and* "nurl"? Especially this normalising is strange.
$r = q("SELECT `id` FROM `contact` WHERE `uid` = (SELECT `uid` FROM `user` WHERE `nickname` = '%s' LIMIT 1)
AND `nick` = '%s' AND NOT `self` AND (`url` LIKE '%%%s%%' OR `nurl` LIKE '%%%s%%') AND NOT `blocked` AND NOT `pending` LIMIT 1",
@ -2959,9 +2939,19 @@ class DFRN
DBA::escape($baseurl),
DBA::escape($nurl)
);
if ((! DBA::isResult($r)) || $r[0]['id'] == remote_user()) {
if ((! DBA::isResult($r))) {
return;
}
// test if redirect authentication already succeeded
// Note that "contact" in the sense used in the $contact_nick argument to this function
// and the sense in the $remote[]["cid"] in the session are opposite.
// In the session variable the user currently fetching is the contact
// while $contact_nick is the nick of tho user who owns the stuff being fetched.
foreach (\Friendica\Core\Session::get('remote', []) as $visitor) {
if ($visitor['uid'] == $contact_uid && $visitor['cid'] == $r[0]['id']) {
return;
}
}
$r = q("SELECT * FROM contact WHERE nick = '%s'
AND network = '%s' AND uid = %d AND url LIKE '%%%s%%' LIMIT 1",

View file

@ -37,6 +37,7 @@ use Friendica\Util\Map;
use Friendica\Util\Network;
use Friendica\Util\Strings;
use Friendica\Util\XML;
use Friendica\Worker\Delivery;
use SimpleXMLElement;
/**
@ -144,7 +145,7 @@ class Diaspora
*/
private static function getRelayContact($server_url)
{
$fields = ['batch', 'id', 'name', 'network', 'archive', 'blocked'];
$fields = ['batch', 'id', 'name', 'network', 'protocol', 'archive', 'blocked'];
// Fetch the relay contact
$condition = ['uid' => 0, 'nurl' => Strings::normaliseLink($server_url),
@ -2147,13 +2148,9 @@ class Diaspora
if ($comment['id'] == $comment['parent']) {
continue;
}
if ($comment['verb'] == ACTIVITY_POST) {
$cmd = $comment['self'] ? 'comment-new' : 'comment-import';
} else {
$cmd = $comment['self'] ? 'like' : 'comment-import';
}
Logger::log("Send ".$cmd." for item ".$comment['id']." to contact ".$contact_id, Logger::DEBUG);
Worker::add(PRIORITY_HIGH, 'Delivery', $cmd, $comment['id'], $contact_id);
Logger::info('Deliver participation', ['item' => $comment['id'], 'contact' => $contact_id]);
Worker::add(PRIORITY_HIGH, 'Delivery', Delivery::POST, $comment['id'], $contact_id);
}
DBA::close($comments);
@ -3110,13 +3107,12 @@ class Diaspora
* @param string $envelope The message that is to be transmitted
* @param bool $public_batch Is it a public post?
* @param string $guid message guid
* @param bool $no_defer Don't defer a failing delivery
*
* @return int Result of the transmission
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function transmit(array $owner, array $contact, $envelope, $public_batch, $guid = "", $no_defer = false)
private static function transmit(array $owner, array $contact, $envelope, $public_batch, $guid = "")
{
$enabled = intval(Config::get("system", "diaspora_enabled"));
if (!$enabled) {
@ -3155,20 +3151,6 @@ class Diaspora
Logger::log("transmit: ".$logid."-".$guid." to ".$dest_url." returns: ".$return_code);
if (!$return_code || (($return_code == 503) && (stristr($postResult->getHeader(), "retry-after")))) {
if (!$no_defer && !empty($contact['contact-type']) && ($contact['contact-type'] != Contact::TYPE_RELAY)) {
Logger::info('defer message', ['log' => $logid, 'guid' => $guid, 'destination' => $dest_url]);
// defer message for redelivery
Worker::defer();
}
// The message could not be delivered. We mark the contact as "dead"
Contact::markForArchival($contact);
} elseif (($return_code >= 200) && ($return_code <= 299)) {
// We successfully delivered a message, the contact is alive
Contact::unmarkForArchival($contact);
}
return $return_code ? $return_code : -1;
}
@ -3197,13 +3179,12 @@ class Diaspora
* @param array $message The message data
* @param bool $public_batch Is it a public post?
* @param string $guid message guid
* @param bool $no_defer Don't defer a failing delivery
*
* @return int Result of the transmission
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
private static function buildAndTransmit(array $owner, array $contact, $type, $message, $public_batch = false, $guid = "", $no_defer = false)
private static function buildAndTransmit(array $owner, array $contact, $type, $message, $public_batch = false, $guid = "")
{
$msg = self::buildPostXml($type, $message);
@ -3217,7 +3198,7 @@ class Diaspora
$envelope = self::buildMessage($msg, $owner, $contact, $owner['uprvkey'], $contact['pubkey'], $public_batch);
$return_code = self::transmit($owner, $contact, $envelope, $public_batch, $guid, $no_defer);
$return_code = self::transmit($owner, $contact, $envelope, $public_batch, $guid);
Logger::log("guid: ".$guid." result ".$return_code, Logger::DEBUG);
@ -3562,6 +3543,7 @@ class Diaspora
$public = ($item["private"] ? "false" : "true");
$created = DateTimeFormat::utc($item["created"], DateTimeFormat::ATOM);
$edited = DateTimeFormat::utc($item["edited"] ?? $item["created"], DateTimeFormat::ATOM);
// Detect a share element and do a reshare
if (!$item['private'] && ($ret = self::isReshare($item["body"]))) {
@ -3616,6 +3598,7 @@ class Diaspora
$message = ["author" => $myaddr,
"guid" => $item["guid"],
"created_at" => $created,
"edited_at" => $edited,
"public" => $public,
"text" => $body,
"provider_display_name" => $item["app"],
@ -3794,11 +3777,13 @@ class Diaspora
$text = html_entity_decode(BBCode::toMarkdown($body));
$created = DateTimeFormat::utc($item["created"], DateTimeFormat::ATOM);
$edited = DateTimeFormat::utc($item["edited"], DateTimeFormat::ATOM);
$comment = [
"author" => self::myHandle($owner),
"guid" => $item["guid"],
"created_at" => $created,
"edited_at" => $edited,
"parent_guid" => $toplevel_item["guid"],
"text" => $text,
"author_signature" => ""
@ -3834,7 +3819,7 @@ class Diaspora
} elseif (in_array($item["verb"], [ACTIVITY_LIKE, ACTIVITY_DISLIKE])) {
$message = self::constructLike($item, $owner);
$type = "like";
} elseif (!in_array($item["verb"], [ACTIVITY_FOLLOW])) {
} elseif (!in_array($item["verb"], [ACTIVITY_FOLLOW, ACTIVITY_TAG])) {
$message = self::constructComment($item, $owner);
$type = "comment";
}
@ -4211,9 +4196,10 @@ class Diaspora
$message = self::createProfileData($uid);
// @ToDo Split this into single worker jobs
foreach ($recips as $recip) {
Logger::log("Send updated profile data for user ".$uid." to contact ".$recip["id"], Logger::DEBUG);
self::buildAndTransmit($owner, $recip, "profile", $message, false, '', true);
self::buildAndTransmit($owner, $recip, "profile", $message);
}
}

View file

@ -417,13 +417,6 @@ class OStatus
$author = self::fetchAuthor($xpath, $entry, $importer, $contact, $stored);
}
$value = XML::getFirstNodeValue($xpath, 'atom:author/poco:preferredUsername/text()', $entry);
if ($value != "") {
$nickname = $value;
} else {
$nickname = $author["author-name"];
}
$item = array_merge($header, $author);
$item["uri"] = XML::getFirstNodeValue($xpath, 'atom:id/text()', $entry);
@ -463,7 +456,7 @@ class OStatus
}
if ($item["verb"] == ACTIVITY_FOLLOW) {
Contact::addRelationship($importer, $contact, $item, $nickname);
Contact::addRelationship($importer, $contact, $item);
continue;
}
@ -745,7 +738,7 @@ class OStatus
self::$conv_list[$conversation] = true;
$curlResult = Network::curl($conversation, false, $redirects, ['accept_content' => 'application/atom+xml, text/html']);
$curlResult = Network::curl($conversation, false, ['accept_content' => 'application/atom+xml, text/html']);
if (!$curlResult->isSuccess()) {
return;
@ -938,7 +931,7 @@ class OStatus
}
$stored = false;
$curlResult = Network::curl($related, false, $redirects, ['accept_content' => 'application/atom+xml, text/html']);
$curlResult = Network::curl($related, false, ['accept_content' => 'application/atom+xml, text/html']);
if (!$curlResult->isSuccess()) {
return;
@ -1515,7 +1508,7 @@ class OStatus
$author->appendChild($urls);
}
XML::addElement($doc, $author, "followers", "", ["url" => System::baseUrl()."/viewcontacts/".$owner["nick"]]);
XML::addElement($doc, $author, "followers", "", ["url" => System::baseUrl() . "/profile/" . $owner["nick"] . "/contacts/followers"]);
XML::addElement($doc, $author, "statusnet:profile_info", "", ["local_id" => $owner["uid"]]);
if ($profile["publish"]) {

View file

@ -665,7 +665,7 @@ class PortableContact
$nodeinfo2_url = '';
foreach ($nodeinfo['links'] as $link) {
if (!is_array($link) || empty($link['rel'])) {
if (!is_array($link) || empty($link['rel']) || empty($link['href'])) {
Logger::log('Invalid nodeinfo format for ' . $server_url, Logger::DEBUG);
continue;
}
@ -749,7 +749,7 @@ class PortableContact
$friendica = false;
$gnusocial = false;
if (is_array($nodeinfo['protocols']['inbound'])) {
if (!empty($nodeinfo['protocols']['inbound']) && is_array($nodeinfo['protocols']['inbound'])) {
foreach ($nodeinfo['protocols']['inbound'] as $inbound) {
if ($inbound == 'diaspora') {
$diaspora = true;
@ -1004,7 +1004,7 @@ 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
$curlResult = Network::curl($server_url."/.well-known/host-meta", false, $redirects, ['timeout' => 20]);
$curlResult = Network::curl($server_url."/.well-known/host-meta", false, ['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.
@ -1021,7 +1021,7 @@ class PortableContact
$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
$curlResult = Network::curl($server_url."/.well-known/host-meta", false, $redirects, ['timeout' => 20]);
$curlResult = Network::curl($server_url."/.well-known/host-meta", false, ['timeout' => 20]);
// Quit if there is a timeout
if ($curlResult->isTimeout()) {
@ -1624,7 +1624,7 @@ class PortableContact
if (!empty($accesstoken)) {
$api = 'https://instances.social/api/1.0/instances/list?count=0';
$header = ['Authorization: Bearer '.$accesstoken];
$curlResult = Network::curl($api, false, $redirects, ['headers' => $header]);
$curlResult = Network::curl($api, false, ['headers' => $header]);
if ($curlResult->isSuccess()) {
$servers = json_decode($curlResult->getBody(), true);

View file

@ -32,10 +32,16 @@ class Emailer
* @return bool
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function send($params)
public static function send(array $params)
{
$params['sent'] = false;
Hook::callAll('emailer_send_prepare', $params);
if ($params['sent']) {
return true;
}
$email_textonly = false;
if (!empty($params['uid'])) {
$email_textonly = PConfig::get($params['uid'], "system", "email_textonly");
@ -87,11 +93,16 @@ class Emailer
'subject' => $messageSubject,
'body' => $multipartMessageBody,
'headers' => $messageHeader,
'parameters' => $sendmail_params
'parameters' => $sendmail_params,
'sent' => false,
];
Hook::callAll("emailer_send", $hookdata);
if ($hookdata['sent']) {
return true;
}
$res = mail(
$hookdata['to'],
$hookdata['subject'],

View file

@ -455,7 +455,7 @@ class HTTPSignature
$curl_opts = $opts;
$curl_opts['header'] = $headers;
$curlResult = Network::curl($request, false, $redirects, $curl_opts);
$curlResult = Network::curl($request, false, $curl_opts);
$return_code = $curlResult->getReturnCode();
Logger::log('Fetched for user ' . $uid . ' from ' . $request . ' returned ' . $return_code, Logger::DEBUG);

View file

@ -68,9 +68,16 @@ class JsonLD
}
catch (Exception $e) {
$normalized = false;
Logger::error('normalise error');
// Sooner or later we should log some details as well - but currently this leads to memory issues
// Logger::log('normalise error:' . substr(print_r($e, true), 0, 10000), Logger::DEBUG);
$messages = [];
$currentException = $e;
do {
$messages[] = $currentException->getMessage();
} while($currentException = $currentException->getPrevious());
Logger::warning('JsonLD normalize error');
Logger::notice('JsonLD normalize error', ['messages' => $messages]);
Logger::info('JsonLD normalize error', ['trace' => $e->getTraceAsString()]);
Logger::debug('JsonLD normalize error', ['jsonobj' => $jsonobj]);
}
return $normalized;

View file

@ -23,19 +23,19 @@ class Network
*
* @brief Curl wrapper
* @param string $url URL to fetch
* @param boolean $binary default false
* @param bool $binary default false
* TRUE if asked to return binary results (file download)
* @param integer $redirects The recursion counter for internal use - default 0
* @param integer $timeout Timeout in seconds, default system config value or 60 seconds
* @param int $timeout Timeout in seconds, default system config value or 60 seconds
* @param string $accept_content supply Accept: header with 'accept_content' as the value
* @param string $cookiejar Path to cookie jar file
* @param int $redirects The recursion counter for internal use - default 0
*
* @return string The fetched content
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function fetchUrl($url, $binary = false, &$redirects = 0, $timeout = 0, $accept_content = null, $cookiejar = '')
public static function fetchUrl(string $url, bool $binary = false, int $timeout = 0, string $accept_content = '', string $cookiejar = '', int &$redirects = 0)
{
$ret = self::fetchUrlFull($url, $binary, $redirects, $timeout, $accept_content, $cookiejar);
$ret = self::fetchUrlFull($url, $binary, $timeout, $accept_content, $cookiejar, $redirects);
return $ret->getBody();
}
@ -48,26 +48,27 @@ class Network
*
* @brief Curl wrapper with array of return values.
* @param string $url URL to fetch
* @param boolean $binary default false
* @param bool $binary default false
* TRUE if asked to return binary results (file download)
* @param integer $redirects The recursion counter for internal use - default 0
* @param integer $timeout Timeout in seconds, default system config value or 60 seconds
* @param int $timeout Timeout in seconds, default system config value or 60 seconds
* @param string $accept_content supply Accept: header with 'accept_content' as the value
* @param string $cookiejar Path to cookie jar file
* @param int $redirects The recursion counter for internal use - default 0
*
* @return CurlResult With all relevant information, 'body' contains the actual fetched content.
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function fetchUrlFull($url, $binary = false, &$redirects = 0, $timeout = 0, $accept_content = null, $cookiejar = '')
public static function fetchUrlFull(string $url, bool $binary = false, int $timeout = 0, string $accept_content = '', string $cookiejar = '', int &$redirects = 0)
{
return self::curl(
$url,
$binary,
$redirects,
['timeout'=>$timeout,
'accept_content'=>$accept_content,
'cookiejar'=>$cookiejar
]
[
'timeout' => $timeout,
'accept_content' => $accept_content,
'cookiejar' => $cookiejar
],
$redirects
);
}
@ -75,9 +76,8 @@ class Network
* @brief fetches an URL.
*
* @param string $url URL to fetch
* @param boolean $binary default false
* @param bool $binary default false
* TRUE if asked to return binary results (file download)
* @param int $redirects The recursion counter for internal use - default 0
* @param array $opts (optional parameters) assoziative array with:
* 'accept_content' => supply Accept: header with 'accept_content' as the value
* 'timeout' => int Timeout in seconds, default system config value or 60 seconds
@ -86,11 +86,12 @@ class Network
* 'nobody' => only return the header
* 'cookiejar' => path to cookie jar file
* 'header' => header array
* @param int $redirects The recursion counter for internal use - default 0
*
* @return CurlResult
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function curl($url, $binary = false, &$redirects = 0, $opts = [])
public static function curl(string $url, bool $binary = false, array $opts = [], int &$redirects = 0)
{
$stamp1 = microtime(true);
@ -227,7 +228,7 @@ class Network
$redirects++;
Logger::log('curl: redirect ' . $url . ' to ' . $curlResponse->getRedirectUrl());
@curl_close($ch);
return self::curl($curlResponse->getRedirectUrl(), $binary, $redirects, $opts);
return self::curl($curlResponse->getRedirectUrl(), $binary, $opts, $redirects);
}
@curl_close($ch);
@ -242,14 +243,14 @@ class Network
*
* @param string $url URL to post
* @param mixed $params array of POST variables
* @param string $headers HTTP headers
* @param integer $redirects Recursion counter for internal use - default = 0
* @param integer $timeout The timeout in seconds, default system config value or 60 seconds
* @param array $headers HTTP headers
* @param int $redirects Recursion counter for internal use - default = 0
* @param int $timeout The timeout in seconds, default system config value or 60 seconds
*
* @return CurlResult The content
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function post($url, $params, $headers = null, &$redirects = 0, $timeout = 0)
public static function post(string $url, $params, array $headers = [], int $timeout = 0, int &$redirects = 0)
{
$stamp1 = microtime(true);
@ -285,7 +286,7 @@ class Network
}
if (defined('LIGHTTPD')) {
if (!is_array($headers)) {
if (empty($headers)) {
$headers = ['Expect:'];
} else {
if (!in_array('Expect:', $headers)) {
@ -294,7 +295,7 @@ class Network
}
}
if ($headers) {
if (!empty($headers)) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
}
@ -351,7 +352,7 @@ class Network
* @return string|boolean The actual working URL, false else
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function isUrlValid($url)
public static function isUrlValid(string $url)
{
if (Config::get('system', 'disable_url_validation')) {
return $url;
@ -381,9 +382,8 @@ class Network
*
* @param string $addr The email address
* @return boolean True if it's a valid email address, false if it's not
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function isEmailDomainValid($addr)
public static function isEmailDomainValid(string $addr)
{
if (Config::get('system', 'disable_email_validation')) {
return true;
@ -413,9 +413,8 @@ class Network
*
* @param string $url URL which get tested
* @return boolean True if url is allowed otherwise return false
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function isUrlAllowed($url)
public static function isUrlAllowed(string $url)
{
$h = @parse_url($url);
@ -460,7 +459,7 @@ class Network
*
* @return boolean
*/
public static function isUrlBlocked($url)
public static function isUrlBlocked(string $url)
{
$host = @parse_url($url, PHP_URL_HOST);
if (!$host) {
@ -491,7 +490,7 @@ class Network
* or if allowed list is not configured
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function isEmailDomainAllowed($email)
public static function isEmailDomainAllowed(string $email)
{
$domain = strtolower(substr($email, strpos($email, '@') + 1));
if (!$domain) {
@ -516,7 +515,7 @@ class Network
* @param array $domain_list
* @return boolean
*/
public static function isDomainAllowed($domain, array $domain_list)
public static function isDomainAllowed(string $domain, array $domain_list)
{
$found = false;
@ -531,7 +530,7 @@ class Network
return $found;
}
public static function lookupAvatarByEmail($email)
public static function lookupAvatarByEmail(string $email)
{
$avatar['size'] = 300;
$avatar['email'] = $email;
@ -554,7 +553,7 @@ class Network
* @param string $url Any user-submitted URL that may contain tracking params
* @return string The same URL stripped of tracking parameters
*/
public static function stripTrackingQueryParams($url)
public static function stripTrackingQueryParams(string $url)
{
$urldata = parse_url($url);
if (!empty($urldata["query"])) {
@ -613,7 +612,7 @@ class Network
* @return string A canonical URL
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function finalUrl($url, $depth = 1, $fetchbody = false)
public static function finalUrl(string $url, int $depth = 1, bool $fetchbody = false)
{
$a = \get_app();
@ -724,7 +723,7 @@ class Network
* @param string $url2
* @return string The matching part
*/
public static function getUrlMatch($url1, $url2)
public static function getUrlMatch(string $url1, string $url2)
{
if (($url1 == "") || ($url2 == "")) {
return "";
@ -812,7 +811,7 @@ class Network
*
* @return string The glued URL
*/
public static function unparseURL($parsed)
public static function unparseURL(array $parsed)
{
$get = function ($key) use ($parsed) {
return isset($parsed[$key]) ? $parsed[$key] : null;
@ -844,7 +843,7 @@ class Network
*
* @return string switched URL
*/
public static function switchScheme($url)
public static function switchScheme(string $url)
{
$scheme = parse_url($url, PHP_URL_SCHEME);
if (empty($scheme)) {

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