Merge branch 'master' 2019.12 into develop
This commit is contained in:
@@ -1,11 +1,59 @@
|
||||
Version 2019.12-dev (unreleased)
|
||||
Version 2019.12 (2019-12-23)
|
||||
Friendica Core:
|
||||
Updates to the translations (CS, DE, ET, JA, NL, PL) [translation teams]
|
||||
Updates to the documentation [copiis, MrPetovan, stom79, tobiasd]
|
||||
Updates to the themes (all) [annando, hoergen, MrPetovan, tobiasd]
|
||||
General code refactoring and enhancements [annando, MrPetovan, nupplaphil, tobiasd]
|
||||
Enhanced the manage functionality [annando]
|
||||
Enhanced the federation with ActivityPub and Diaspora* protocol detection and contact requests [annando]
|
||||
Enhanced federation with pixelfed and peertube [annando]
|
||||
Enhanced how the API handles quoted postings [annando]
|
||||
Enhanced the attachment removal by the API [annando]
|
||||
Enhanced the 2FA field for mobile devices [nupplaphil]
|
||||
Enhanced handling of re-shares [annando]
|
||||
Enhanced the ACL dialog [annando, MrPetovan]
|
||||
Enhanced transmission of postings by email and email handling in general [annando]
|
||||
Enhanced the updating process of contacts [annando]
|
||||
Enhanced the import of RSS/Atom feeds [annando]
|
||||
Enhanced the registration form for require approval setups [tobiasd]
|
||||
Enhanced the follow process for the Diaspora* protocol [annando]
|
||||
Enhanced the display of the saved searched [AlfredSK]
|
||||
Enhanced the display of image titles [annando]
|
||||
Enhanced the handling of OpenID [annando]
|
||||
Enhanced the Vagrant devel VM [tobiasd]
|
||||
Enhanced handling of HTML special entities [nathilia-peirce]
|
||||
Enhanced hashes by using HMAC [nathilia-peirce]
|
||||
Enhancements to the ActivityPub implementation [annando]
|
||||
Fixed a problem with delivery of direct messages over ActivityPub [annando]
|
||||
Fixed some problems with the remote auth functionality [annando]
|
||||
Fixed an issue that prevented notifications for deleted postings be deleted themselves [annando]
|
||||
Fixed a problem connecting to forums [annando]
|
||||
Fixed messages in the admin panel [nupplaphil]
|
||||
Fixed a problem when the log-file was not write-able [nupplaphil]
|
||||
Fixed a problem with the caching directory for the password exposure check [MrPetovan]
|
||||
Fixed a bug with detecting the browser language [nupplaphil]
|
||||
Smart threading is now the default, but can be switched off by the user [annando]
|
||||
Clarification: Posted order is now arrival order [annando]
|
||||
Added router configuration file [nupplaphil]
|
||||
Added drone.io as CI service [nupplaphil]
|
||||
Added the ability to pin postings on account walls [annando]
|
||||
Added various new API endpoints [annando, MrPetovan]
|
||||
Added hooks for the email fetching process [annando]
|
||||
Added support for nodeinfo 2 [annando]
|
||||
Added export and import of followed contact data [tobiasd]
|
||||
Added links to tag and category overview in the footer of postings [tobiasd]
|
||||
Added config switch to use BCC instead of CC for ActivityPub delivery of non-public postings [annando]
|
||||
|
||||
Friendica Addons:
|
||||
Update to the translations (CA, DE, ET) [translation teams]
|
||||
buffer
|
||||
marked as unsupported [annando]
|
||||
Discourse
|
||||
New addon to integrate Discourse discussions [annando]
|
||||
gnot
|
||||
UI improvements [tobiasd]
|
||||
js_upload
|
||||
The addon got rewritten to adopt the new ACL [MrPetovan]
|
||||
mailstream:
|
||||
Support for new img format was added [mexon]
|
||||
BB Code is now included as plaintext [mexon]
|
||||
@@ -13,7 +61,12 @@ Version 2019.12-dev (unreleased)
|
||||
ActivityPub "announce" notifications are not included [mexon]
|
||||
|
||||
Closed Issues:
|
||||
1071, 7548, 7657, 7681
|
||||
989, 1071, 1188, 1334, 2537, 3229, 3231, 3385, 4112, 4442, 4451,
|
||||
5048, 5568, 5802, 6865, 7190, 7308, 7316, 7418, 7613, 7657, 7659,
|
||||
7664, 7671, 7679, 7681, 7682, 7685, 7688, 7691, 7702, 7707, 7709,
|
||||
7718, 7733, 7740, 7747, 7756, 7766, 7773, 7776, 7778, 7781, 7821,
|
||||
7825, 7834, 7863, 7868, 7880, 7888, 7889, 7902, 7914, 7920, 7946,
|
||||
7953, 7978
|
||||
|
||||
Version 2019.09 (2019-09-29)
|
||||
Friendica Core:
|
||||
|
||||
+9
-3
@@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
23n
|
||||
Abinoam P. Marques Jr.
|
||||
Abraham Pérez Hernández
|
||||
@@ -47,6 +45,7 @@ bufalo1973
|
||||
Calango Jr
|
||||
Carlos Solís
|
||||
Carsten Pfeiffer
|
||||
Casper
|
||||
Cat Gray
|
||||
Chris Case
|
||||
Christian González
|
||||
@@ -66,6 +65,7 @@ David Martín Miranda
|
||||
David Rabel
|
||||
Dean Townsley
|
||||
Denis Chenu
|
||||
dependabot[bot]
|
||||
Devlon Duthie
|
||||
Diego Souza
|
||||
Domovoy
|
||||
@@ -115,6 +115,7 @@ Jens Tautenhahn
|
||||
jensp
|
||||
Jeroen De Meerleer
|
||||
jeroenpraat
|
||||
Joan Bar
|
||||
JOduMonT
|
||||
Johannes Schwab
|
||||
John Brazil
|
||||
@@ -123,11 +124,13 @@ Jonny Tischbein
|
||||
Josef Moravek
|
||||
juanman
|
||||
julia.domagalska
|
||||
Julio Cova
|
||||
Karel
|
||||
Karolina
|
||||
Keith Fernie
|
||||
Klaus Weidenbach
|
||||
Koyu Berteon
|
||||
kPherox
|
||||
Kris
|
||||
Lea1995polish
|
||||
Leberwurscht
|
||||
@@ -162,18 +165,20 @@ Michalina
|
||||
Mike Macgirvin
|
||||
miqrogroove
|
||||
mytbk
|
||||
nathilia-peirce
|
||||
Nicola Spanti
|
||||
Olaf Conradi
|
||||
Oliver
|
||||
Olivier
|
||||
Olivier Mehani
|
||||
Olivier Migeot
|
||||
Ozero Dien
|
||||
ozero dien
|
||||
Paolo Wave
|
||||
Pascal
|
||||
Pascal Deklerck
|
||||
Pavel Morozov
|
||||
PerigGouanvic
|
||||
peter
|
||||
Peter Liebetrau
|
||||
peturisfeld
|
||||
Phigger Phigger
|
||||
@@ -193,6 +198,7 @@ Ralph
|
||||
Ratten
|
||||
rcmaniac
|
||||
rebeka-catalina
|
||||
René Wagner
|
||||
repat
|
||||
Ricardo Pereira
|
||||
Rik 4
|
||||
|
||||
@@ -32,7 +32,7 @@ use Friendica\Util\DateTimeFormat;
|
||||
|
||||
define('FRIENDICA_PLATFORM', 'Friendica');
|
||||
define('FRIENDICA_CODENAME', 'Dalmatian Bellflower');
|
||||
define('FRIENDICA_VERSION', '2019.12-dev');
|
||||
define('FRIENDICA_VERSION', '2019.12');
|
||||
define('DFRN_PROTOCOL_VERSION', '2.23');
|
||||
define('NEW_UPDATE_ROUTINE_VERSION', 1170);
|
||||
|
||||
|
||||
+6
-2
@@ -1,6 +1,6 @@
|
||||
-- ------------------------------------------
|
||||
-- Friendica 2019.12-dev (Dalmatian Bellflower)
|
||||
-- DB_UPDATE_VERSION 1324
|
||||
-- Friendica 2019.12-rc (Dalmatian Bellflower)
|
||||
-- DB_UPDATE_VERSION 1326
|
||||
-- ------------------------------------------
|
||||
|
||||
|
||||
@@ -66,6 +66,9 @@ CREATE TABLE IF NOT EXISTS `apcontact` (
|
||||
`pubkey` text COMMENT '',
|
||||
`baseurl` varchar(255) COMMENT 'baseurl of the ap contact',
|
||||
`generator` varchar(255) COMMENT 'Name of the contact\'s system',
|
||||
`following_count` int unsigned DEFAULT 0 COMMENT 'Number of following contacts',
|
||||
`followers_count` int unsigned DEFAULT 0 COMMENT 'Number of followers',
|
||||
`statuses_count` int unsigned DEFAULT 0 COMMENT 'Number of posts',
|
||||
`updated` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
|
||||
PRIMARY KEY(`url`),
|
||||
INDEX `addr` (`addr`(32)),
|
||||
@@ -634,6 +637,7 @@ CREATE TABLE IF NOT EXISTS `item` (
|
||||
INDEX `resource-id` (`resource-id`),
|
||||
INDEX `deleted_changed` (`deleted`,`changed`),
|
||||
INDEX `uid_wall_changed` (`uid`,`wall`,`changed`),
|
||||
INDEX `mention_uid_id` (`mention`,`uid`,`id`),
|
||||
INDEX `uid_eventid` (`uid`,`event-id`),
|
||||
INDEX `icid` (`icid`),
|
||||
INDEX `iaid` (`iaid`),
|
||||
|
||||
+16
-5
@@ -11,14 +11,25 @@ Authentication is the same as described in [Using the APIs](help/api#Authenticat
|
||||
|
||||
## Entities
|
||||
|
||||
These endpoints use the [Mastodon API entities](https://docs.joinmastodon.org/api/entities/).
|
||||
These endpoints use the [Mastodon API entities](https://docs.joinmastodon.org/entities/).
|
||||
|
||||
## Implemented endpoints
|
||||
|
||||
- [GET /api/v1/follow_requests](https://docs.joinmastodon.org/api/rest/follow-requests/#get-api-v1-follow-requests)
|
||||
- [`GET /api/v1/follow_requests`](https://docs.joinmastodon.org/methods/accounts/follow_requests#pending-follows)
|
||||
- Returned IDs are specific to follow requests
|
||||
- [`POST /api/v1/follow_requests/:id/authorize`](https://docs.joinmastodon.org/methods/accounts/follow_requests#accept-follow)
|
||||
- `:id` is a follow request ID, not a regular account id
|
||||
- [`POST /api/v1/follow_requests/:id/reject`](https://docs.joinmastodon.org/methods/accounts/follow_requests#reject-follow)
|
||||
- `:id` is a follow request ID, not a regular account id
|
||||
- `POST /api/v1/follow_requests/:id/ignore`
|
||||
- Friendica-specific, hides the follow request from the list and prevents the remote contact from retrying.
|
||||
- `:id` is a follow request ID, not a regular account id
|
||||
- Returns a [Relationship](https://docs.joinmastodon.org/entities/relationship) object.
|
||||
|
||||
|
||||
- [`GET /api/v1/instance`](https://docs.joinmastodon.org/methods/instance#fetch-instance)
|
||||
- [`GET /api/v1/instance/peers`](https://docs.joinmastodon.org/methods/instance#list-of-connected-domains)
|
||||
|
||||
## Non-implemented endpoints
|
||||
|
||||
- [POST /api/v1/follow_requests/:id/authorize](https://docs.joinmastodon.org/api/rest/follow-requests/#post-api-v1-follow-requests-id-authorize)
|
||||
- [POST /api/v1/follow_requests/:id/reject](https://docs.joinmastodon.org/api/rest/follow-requests/#post-api-v1-follow-requests-id-reject)
|
||||
|
||||
- [`GET /api/v1/instance/activity`](https://docs.joinmastodon.org/methods/instance#weekly-activity)
|
||||
|
||||
+1
-1
@@ -172,7 +172,7 @@ Here is a list of known working clients:
|
||||
|
||||
* Android
|
||||
* [Friendiqa](https://git.friendi.ca/lubuwest/Friendiqa) (available in Google Playstore or from a binary repository you can add to [F-Droid](https://freunde.ma-nic.de/display/3e98eba8185a13c5bdbf3d1539646854))
|
||||
* [Fedilab](https://gitlab.com/tom79/mastalab) (available in F-Droid and Google stores)
|
||||
* [Fedilab](https://fedilab.app/) (available in F-Droid and Google stores)
|
||||
* [DiCa](https://dica.mixi.cool/)
|
||||
* AndStatus
|
||||
* Twidere
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ Friendica Documentation and Resources
|
||||
* [Move classes to `src`](help/Developer-How-To-Move-Classes-to-src)
|
||||
* [Run tests](help/Tests)
|
||||
* Reference
|
||||
* [Twitter/GNU Social API Functions](help/api)
|
||||
* [API endpoints](help/api)
|
||||
* [Code (Doxygen generated - sets cookies)](doc/html/)
|
||||
* [Protocol Documentation](help/Protocol)
|
||||
* [Database schema documentation](help/database)
|
||||
|
||||
+5
-5
@@ -30,7 +30,7 @@ The account will expire after 7 days, but you can ask the server admin to keep y
|
||||
|
||||
### Requirements
|
||||
|
||||
* Apache with mod-rewrite enabled and "Options All" so you can use a local .htaccess file
|
||||
* Apache with mod-rewrite enabled and "Options All" so you can use a local `.htaccess` file
|
||||
* PHP 7+ (PHP 7.1+ is recommended for performance and official support)
|
||||
* PHP *command line* access with register_argc_argv set to true in the php.ini file
|
||||
* Curl, GD, PDO, MySQLi, hash, xml, zip and OpenSSL extensions
|
||||
@@ -63,8 +63,7 @@ If this is nothing for you, you might be interested in
|
||||
|
||||
Unpack the Friendica files into the root of your web server document area.
|
||||
|
||||
If you copy the directory tree to your webserver, make sure that you also copy
|
||||
`.htaccess-dist` - as "dot" files are often hidden and aren't normally copied.
|
||||
If you copy the directory tree to your webserver, make sure that you also copy `.htaccess-dist` - as "dot" files are often hidden and aren't normally copied.
|
||||
|
||||
**OR**
|
||||
|
||||
@@ -112,7 +111,8 @@ Please check the [troubleshooting](#Troubleshooting) section if running on MySQL
|
||||
|
||||
### Option A: Run the installer
|
||||
|
||||
Point your web browser to the new site and follow the instructions.
|
||||
Before you point your web browser to the new site you need to copy `.htaccess-dist` to `.htaccess` for Apache installs.
|
||||
Follow the instructions.
|
||||
Please note any error messages and correct these before continuing.
|
||||
|
||||
If you need to specify a port for the connection to the database, you can do so in the host name setting for the database.
|
||||
@@ -328,7 +328,7 @@ If the database resides on the same machine, check that the database server name
|
||||
### 500 Internal Error
|
||||
|
||||
This could be the result of one of our Apache directives not being supported by your version of Apache. Examine your apache server logs.
|
||||
You might remove the line "Options -Indexes" from the .htaccess file if you are using a Windows server as this has been known to cause problems.
|
||||
You might remove the line "Options -Indexes" from the `.htaccess` file if you are using a Windows server as this has been known to cause problems.
|
||||
Also check your file permissions. Your website and all contents must generally be world-readable.
|
||||
|
||||
It is likely that your web server reported the source of the problem in its error log files.
|
||||
|
||||
@@ -107,6 +107,7 @@ Starte MySQL dann neu und es sollte klappen.
|
||||
### Option A: Der manuelle Installer
|
||||
|
||||
Besuche deine Webseite mit deinem Browser und befolge die Anleitung.
|
||||
Bevor du dies tust, kopiere die Datei `.htaccess-dist` nach `.htaccess`, wenn du den Apache Webserver verwendest.
|
||||
Bitte beachte jeden Fehler und korrigiere diese, bevor du fortfährst.
|
||||
|
||||
Falls du einen Port für die Datenbankverbindung angeben musst, kannst du diesen in der Host-Eingabe Zeile angeben.
|
||||
|
||||
+125
-168
@@ -608,11 +608,6 @@ function api_get_user(App $a, $contact_id = null)
|
||||
$contact = DBA::selectFirst('contact', [], ['uid' => 0, 'nurl' => Strings::normaliseLink($url)]);
|
||||
|
||||
if (DBA::isResult($contact)) {
|
||||
// If no nick where given, extract it from the address
|
||||
if (($contact['nick'] == "") || ($contact['name'] == $contact['nick'])) {
|
||||
$contact['nick'] = api_get_nick($contact["url"]);
|
||||
}
|
||||
|
||||
$ret = [
|
||||
'id' => $contact["id"],
|
||||
'id_str' => (string) $contact["id"],
|
||||
@@ -671,11 +666,6 @@ function api_get_user(App $a, $contact_id = null)
|
||||
$countfollowers = 0;
|
||||
$starred = 0;
|
||||
|
||||
// Add a nick if it isn't present there
|
||||
if (($uinfo[0]['nick'] == "") || ($uinfo[0]['name'] == $uinfo[0]['nick'])) {
|
||||
$uinfo[0]['nick'] = api_get_nick($uinfo[0]["url"]);
|
||||
}
|
||||
|
||||
$pcontact_id = Contact::getIdForURL($uinfo[0]['url'], 0, true);
|
||||
|
||||
if (!empty($profile['about'])) {
|
||||
@@ -1419,32 +1409,37 @@ function api_users_search($type)
|
||||
$userlist = [];
|
||||
|
||||
if (!empty($_GET['q'])) {
|
||||
$r = q("SELECT id FROM `contact` WHERE `uid` = 0 AND `name` = '%s'", DBA::escape($_GET["q"]));
|
||||
$contacts = Contact::selectToArray(
|
||||
['id'],
|
||||
[
|
||||
'`uid` = 0 AND (`name` = ? OR `nick` = ? OR `url` = ? OR `addr` = ?)',
|
||||
$_GET['q'],
|
||||
$_GET['q'],
|
||||
$_GET['q'],
|
||||
$_GET['q'],
|
||||
]
|
||||
);
|
||||
|
||||
if (!DBA::isResult($r)) {
|
||||
$r = q("SELECT `id` FROM `contact` WHERE `uid` = 0 AND `nick` = '%s'", DBA::escape($_GET["q"]));
|
||||
}
|
||||
|
||||
if (DBA::isResult($r)) {
|
||||
if (DBA::isResult($contacts)) {
|
||||
$k = 0;
|
||||
foreach ($r as $user) {
|
||||
$user_info = api_get_user($a, $user["id"]);
|
||||
foreach ($contacts as $contact) {
|
||||
$user_info = api_get_user($a, $contact['id']);
|
||||
|
||||
if ($type == "xml") {
|
||||
$userlist[$k++.":user"] = $user_info;
|
||||
if ($type == 'xml') {
|
||||
$userlist[$k++ . ':user'] = $user_info;
|
||||
} else {
|
||||
$userlist[] = $user_info;
|
||||
}
|
||||
}
|
||||
$userlist = ["users" => $userlist];
|
||||
$userlist = ['users' => $userlist];
|
||||
} else {
|
||||
throw new BadRequestException("User ".$_GET["q"]." not found.");
|
||||
throw new NotFoundException('User ' . $_GET['q'] . ' not found.');
|
||||
}
|
||||
} else {
|
||||
throw new BadRequestException("No user specified.");
|
||||
throw new BadRequestException('No search term specified.');
|
||||
}
|
||||
|
||||
return api_format_data("users", $type, $userlist);
|
||||
return api_format_data('users', $type, $userlist);
|
||||
}
|
||||
|
||||
/// @TODO move to top of file or somewhere better
|
||||
@@ -1505,7 +1500,9 @@ function api_search($type)
|
||||
$a = \get_app();
|
||||
$user_info = api_get_user($a);
|
||||
|
||||
if (api_user() === false || $user_info === false) { throw new ForbiddenException(); }
|
||||
if (api_user() === false || $user_info === false) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
if (empty($_REQUEST['q'])) {
|
||||
throw new BadRequestException('q parameter is required.');
|
||||
@@ -1569,7 +1566,21 @@ function api_search($type)
|
||||
}
|
||||
}
|
||||
|
||||
$statuses = Item::selectForUser(api_user(), [], $condition, $params);
|
||||
$statuses = [];
|
||||
|
||||
if (parse_url($searchTerm, PHP_URL_SCHEME) != '') {
|
||||
$id = Item::fetchByLink($searchTerm, api_user());
|
||||
if (!$id) {
|
||||
// Public post
|
||||
$id = Item::fetchByLink($searchTerm);
|
||||
}
|
||||
|
||||
if (!empty($id)) {
|
||||
$statuses = Item::select([], ['id' => $id]);
|
||||
}
|
||||
}
|
||||
|
||||
$statuses = $statuses ?: Item::selectForUser(api_user(), [], $condition, $params);
|
||||
|
||||
$data['status'] = api_format_items(Item::inArray($statuses), $user_info);
|
||||
|
||||
@@ -2147,8 +2158,8 @@ function api_statuses_mentions($type)
|
||||
|
||||
$start = max(0, ($page - 1) * $count);
|
||||
|
||||
$condition = ["`uid` = ? AND `gravity` IN (?, ?) AND `item`.`id` > ? AND `author-id` != ?
|
||||
AND `item`.`parent` IN (SELECT `iid` FROM `thread` WHERE `thread`.`uid` = ? AND `thread`.`mention` AND NOT `thread`.`ignored`)",
|
||||
$condition = ["`uid` = ? AND `gravity` IN (?, ?) AND `item`.`id` > ? AND `author-id` != ? AND `mention`
|
||||
AND `item`.`parent` IN (SELECT `iid` FROM `thread` WHERE `thread`.`uid` = ? AND NOT `thread`.`ignored`)",
|
||||
api_user(), GRAVITY_PARENT, GRAVITY_COMMENT, $since_id, $user_info['pid'], api_user()];
|
||||
|
||||
if ($max_id > 0) {
|
||||
@@ -2486,6 +2497,9 @@ function api_format_messages($item, $recipient, $sender)
|
||||
function api_convert_item($item)
|
||||
{
|
||||
$body = $item['body'];
|
||||
$entities = api_get_entitities($statustext, $body);
|
||||
|
||||
// Add pictures to the attachment array and remove them from the body
|
||||
$attachments = api_get_attachments($body);
|
||||
|
||||
// Workaround for ostatus messages where the title is identically to the body
|
||||
@@ -2542,8 +2556,6 @@ function api_convert_item($item)
|
||||
$statushtml .= BBCode::convert($item['plink']);
|
||||
}
|
||||
|
||||
$entities = api_get_entitities($statustext, $body);
|
||||
|
||||
return [
|
||||
"text" => $statustext,
|
||||
"html" => $statushtml,
|
||||
@@ -2561,17 +2573,19 @@ function api_convert_item($item)
|
||||
*/
|
||||
function api_get_attachments(&$body)
|
||||
{
|
||||
$text = $body;
|
||||
$text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $text);
|
||||
$text = preg_replace("/\[img\=(.*?)\](.*?)\[\/img\]/ism", '[img]$1[/img]', $text);
|
||||
$body = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $body);
|
||||
$body = preg_replace("/\[img\=(.*?)\](.*?)\[\/img\]/ism", '[img]$1[/img]', $body);
|
||||
|
||||
$URLSearchString = "^\[\]";
|
||||
$ret = preg_match_all("/\[img\]([$URLSearchString]*)\[\/img\]/ism", $text, $images);
|
||||
|
||||
if (!$ret) {
|
||||
if (!preg_match_all("/\[img\]([$URLSearchString]*)\[\/img\]/ism", $body, $images)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Remove all embedded pictures, since they are added as attachments
|
||||
foreach ($images[0] as $orig) {
|
||||
$body = str_replace($orig, '', $body);
|
||||
}
|
||||
|
||||
$attachments = [];
|
||||
|
||||
foreach ($images[1] as $image) {
|
||||
@@ -2582,12 +2596,6 @@ function api_get_attachments(&$body)
|
||||
}
|
||||
}
|
||||
|
||||
if (strstr($_SERVER['HTTP_USER_AGENT'] ?? '', 'AndStatus')) {
|
||||
foreach ($images[0] as $orig) {
|
||||
$body = str_replace($orig, "", $body);
|
||||
}
|
||||
}
|
||||
|
||||
return $attachments;
|
||||
}
|
||||
|
||||
@@ -2630,7 +2638,6 @@ function api_get_entitities(&$text, $bbcode)
|
||||
$bbcode = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '#$2', $bbcode);
|
||||
|
||||
$bbcode = preg_replace("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism", '[url=$1]$2[/url]', $bbcode);
|
||||
//$bbcode = preg_replace("/\[url\](.*?)\[\/url\]/ism",'[url=$1]$1[/url]',$bbcode);
|
||||
$bbcode = preg_replace("/\[video\](.*?)\[\/video\]/ism", '[url=$1]$1[/url]', $bbcode);
|
||||
|
||||
$bbcode = preg_replace(
|
||||
@@ -2649,12 +2656,10 @@ function api_get_entitities(&$text, $bbcode)
|
||||
|
||||
$bbcode = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $bbcode);
|
||||
|
||||
//preg_match_all("/\[url\]([$URLSearchString]*)\[\/url\]/ism", $bbcode, $urls1);
|
||||
preg_match_all("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", $bbcode, $urls);
|
||||
|
||||
$ordered_urls = [];
|
||||
foreach ($urls[1] as $id => $url) {
|
||||
//$start = strpos($text, $url, $offset);
|
||||
$start = iconv_strpos($text, $url, 0, "UTF-8");
|
||||
if (!($start === false)) {
|
||||
$ordered_urls[$start] = ["url" => $url, "title" => $urls[2][$id]];
|
||||
@@ -2664,7 +2669,7 @@ function api_get_entitities(&$text, $bbcode)
|
||||
ksort($ordered_urls);
|
||||
|
||||
$offset = 0;
|
||||
//foreach ($urls[1] AS $id=>$url) {
|
||||
|
||||
foreach ($ordered_urls as $url) {
|
||||
if ((substr($url["title"], 0, 7) != "http://") && (substr($url["title"], 0, 8) != "https://")
|
||||
&& !strpos($url["title"], "http://") && !strpos($url["title"], "https://")
|
||||
@@ -2679,7 +2684,6 @@ function api_get_entitities(&$text, $bbcode)
|
||||
}
|
||||
}
|
||||
|
||||
//$start = strpos($text, $url, $offset);
|
||||
$start = iconv_strpos($text, $url["url"], $offset, "UTF-8");
|
||||
if (!($start === false)) {
|
||||
$entities["urls"][] = ["url" => $url["url"],
|
||||
@@ -2706,7 +2710,7 @@ function api_get_entitities(&$text, $bbcode)
|
||||
$ordered_images[$start] = ['url' => $image, 'alt' => ''];
|
||||
}
|
||||
}
|
||||
//$entities["media"] = array();
|
||||
|
||||
$offset = 0;
|
||||
|
||||
foreach ($ordered_images as $image) {
|
||||
@@ -2840,9 +2844,10 @@ function api_format_items_activities($item, $type = "json")
|
||||
'attendyes' => [],
|
||||
'attendno' => [],
|
||||
'attendmaybe' => [],
|
||||
'announce' => [],
|
||||
];
|
||||
|
||||
$condition = ['uid' => $item['uid'], 'thr-parent' => $item['uri']];
|
||||
$condition = ['uid' => $item['uid'], 'thr-parent' => $item['uri'], 'gravity' => GRAVITY_ACTIVITY];
|
||||
$ret = Item::selectForUser($item['uid'], ['author-id', 'verb'], $condition);
|
||||
|
||||
while ($parent_item = Item::fetch($ret)) {
|
||||
@@ -2867,6 +2872,9 @@ function api_format_items_activities($item, $type = "json")
|
||||
case Activity::ATTENDMAYBE:
|
||||
$activities['attendmaybe'][] = $user;
|
||||
break;
|
||||
case Activity::ANNOUNCE:
|
||||
$activities['announce'][] = $user;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -3075,22 +3083,31 @@ function api_format_item($item, $type = "json", $status_user = null, $author_use
|
||||
}
|
||||
|
||||
if (!empty($quoted_item)) {
|
||||
$conv_quoted = api_convert_item($quoted_item);
|
||||
$quoted_status = $status;
|
||||
if ($quoted_item['id'] != $item['id']) {
|
||||
$quoted_status = api_format_item($quoted_item);
|
||||
/// @todo Only remove the attachments that are also contained in the quotes status
|
||||
unset($status['attachments']);
|
||||
unset($status['entities']);
|
||||
} else {
|
||||
$conv_quoted = api_convert_item($quoted_item);
|
||||
$quoted_status = $status;
|
||||
unset($quoted_status['attachments']);
|
||||
unset($quoted_status['entities']);
|
||||
unset($quoted_status['statusnet_conversation_id']);
|
||||
$quoted_status['text'] = $conv_quoted['text'];
|
||||
$quoted_status['statusnet_html'] = $conv_quoted['html'];
|
||||
try {
|
||||
$quoted_status["user"] = api_get_user($a, $quoted_item["author-id"]);
|
||||
} catch (BadRequestException $e) {
|
||||
// user not found. should be found?
|
||||
/// @todo check if the user should be always found
|
||||
$quoted_status["user"] = [];
|
||||
}
|
||||
}
|
||||
unset($quoted_status['friendica_author']);
|
||||
unset($quoted_status['friendica_owner']);
|
||||
unset($quoted_status['friendica_activities']);
|
||||
unset($quoted_status['friendica_private']);
|
||||
unset($quoted_status['statusnet_conversation_id']);
|
||||
$quoted_status['text'] = $conv_quoted['text'];
|
||||
$quoted_status['statusnet_html'] = $conv_quoted['html'];
|
||||
try {
|
||||
$quoted_status["user"] = api_get_user($a, $quoted_item["author-id"]);
|
||||
} catch (BadRequestException $e) {
|
||||
// user not found. should be found?
|
||||
/// @todo check if the user should be always found
|
||||
$quoted_status["user"] = [];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($retweeted_item)) {
|
||||
@@ -3606,6 +3623,7 @@ api_register_func('api/statusnet/version', 'api_statusnet_version', false);
|
||||
*
|
||||
* @param string $type Return type (atom, rss, xml, json)
|
||||
*
|
||||
* @param int $rel A contact relationship constant
|
||||
* @return array|string|void
|
||||
* @throws BadRequestException
|
||||
* @throws ForbiddenException
|
||||
@@ -3614,7 +3632,7 @@ api_register_func('api/statusnet/version', 'api_statusnet_version', false);
|
||||
* @throws UnauthorizedException
|
||||
* @todo use api_format_data() to return data
|
||||
*/
|
||||
function api_ff_ids($type)
|
||||
function api_ff_ids($type, int $rel)
|
||||
{
|
||||
if (!api_user()) {
|
||||
throw new ForbiddenException();
|
||||
@@ -3626,26 +3644,29 @@ function api_ff_ids($type)
|
||||
|
||||
$stringify_ids = $_REQUEST['stringify_ids'] ?? false;
|
||||
|
||||
$r = q(
|
||||
"SELECT `pcontact`.`id` FROM `contact`
|
||||
INNER JOIN `contact` AS `pcontact` ON `contact`.`nurl` = `pcontact`.`nurl` AND `pcontact`.`uid` = 0
|
||||
WHERE `contact`.`uid` = %s AND NOT `contact`.`self`",
|
||||
intval(api_user())
|
||||
$contacts = DBA::p("SELECT `pcontact`.`id`
|
||||
FROM `contact`
|
||||
INNER JOIN `contact` AS `pcontact`
|
||||
ON `contact`.`nurl` = `pcontact`.`nurl`
|
||||
AND `pcontact`.`uid` = 0
|
||||
WHERE `contact`.`uid` = ?
|
||||
AND NOT `contact`.`self`
|
||||
AND `contact`.`rel` IN (?, ?)",
|
||||
api_user(),
|
||||
$rel,
|
||||
Contact::FRIEND
|
||||
);
|
||||
if (!DBA::isResult($r)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ids = [];
|
||||
foreach ($r as $rr) {
|
||||
foreach (DBA::toArray($contacts) as $contact) {
|
||||
if ($stringify_ids) {
|
||||
$ids[] = $rr['id'];
|
||||
$ids[] = $contact['id'];
|
||||
} else {
|
||||
$ids[] = intval($rr['id']);
|
||||
$ids[] = intval($contact['id']);
|
||||
}
|
||||
}
|
||||
|
||||
return api_format_data("ids", $type, ['id' => $ids]);
|
||||
return api_format_data('ids', $type, ['id' => $ids]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3656,11 +3677,14 @@ function api_ff_ids($type)
|
||||
* @return array|string
|
||||
* @throws BadRequestException
|
||||
* @throws ForbiddenException
|
||||
* @throws ImagickException
|
||||
* @throws InternalServerErrorException
|
||||
* @throws UnauthorizedException
|
||||
* @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-ids
|
||||
*/
|
||||
function api_friends_ids($type)
|
||||
{
|
||||
return api_ff_ids($type);
|
||||
return api_ff_ids($type, Contact::SHARING);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3671,11 +3695,14 @@ function api_friends_ids($type)
|
||||
* @return array|string
|
||||
* @throws BadRequestException
|
||||
* @throws ForbiddenException
|
||||
* @throws ImagickException
|
||||
* @throws InternalServerErrorException
|
||||
* @throws UnauthorizedException
|
||||
* @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-ids
|
||||
*/
|
||||
function api_followers_ids($type)
|
||||
{
|
||||
return api_ff_ids($type);
|
||||
return api_ff_ids($type, Contact::FOLLOWER);
|
||||
}
|
||||
|
||||
/// @TODO move to top of file or somewhere better
|
||||
@@ -5064,14 +5091,17 @@ function api_friendica_remoteauth()
|
||||
// traditional DFRN
|
||||
|
||||
$contact = DBA::selectFirst('contact', [], ['uid' => api_user(), 'nurl' => $c_url]);
|
||||
|
||||
if (!DBA::isResult($contact) || ($contact['network'] !== Protocol::DFRN)) {
|
||||
if (!DBA::isResult($contact)) {
|
||||
throw new BadRequestException("Unknown contact");
|
||||
}
|
||||
|
||||
$cid = $contact['id'];
|
||||
|
||||
$dfrn_id = $contact['issued-id'] ?? $contact['dfrn-id'];
|
||||
$dfrn_id = $contact['issued-id'] ?: $contact['dfrn-id'];
|
||||
|
||||
if (($contact['network'] !== Protocol::DFRN) || empty($dfrn_id)) {
|
||||
System::externalRedirect($url ?: $c_url);
|
||||
}
|
||||
|
||||
if ($contact['duplex'] && $contact['issued-id']) {
|
||||
$orig_id = $contact['issued-id'];
|
||||
@@ -5184,94 +5214,25 @@ function api_share_as_retweet(&$item)
|
||||
$reshared_item["created"] = $reshared['posted'];
|
||||
$reshared_item["edited"] = $reshared['posted'];
|
||||
|
||||
// Try to fetch the original item
|
||||
if (!empty($reshared['guid'])) {
|
||||
$condition = ['guid' => $reshared['guid'], 'uid' => [0, $item['uid']]];
|
||||
} elseif (!empty($reshared_item['plink']) && ($original_id = Item::searchByLink($reshared_item['plink']))) {
|
||||
$condition = ['id' => $original_id];
|
||||
} else {
|
||||
$condition = [];
|
||||
}
|
||||
|
||||
if (!empty($condition)) {
|
||||
$original_item = Item::selectFirst([], $condition);
|
||||
if (DBA::isResult($original_item)) {
|
||||
$reshared_item = array_merge($reshared_item, $original_item);
|
||||
}
|
||||
}
|
||||
|
||||
return $reshared_item;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $profile
|
||||
*
|
||||
* @return string|false
|
||||
* @throws InternalServerErrorException
|
||||
* @todo remove trailing junk from profile url
|
||||
* @todo pump.io check has to check the website
|
||||
*/
|
||||
function api_get_nick($profile)
|
||||
{
|
||||
$nick = "";
|
||||
|
||||
$r = q(
|
||||
"SELECT `nick` FROM `contact` WHERE `uid` = 0 AND `nurl` = '%s'",
|
||||
DBA::escape(Strings::normaliseLink($profile))
|
||||
);
|
||||
|
||||
if (DBA::isResult($r)) {
|
||||
$nick = $r[0]["nick"];
|
||||
}
|
||||
|
||||
if (!$nick == "") {
|
||||
$r = q(
|
||||
"SELECT `nick` FROM `contact` WHERE `uid` = 0 AND `nurl` = '%s'",
|
||||
DBA::escape(Strings::normaliseLink($profile))
|
||||
);
|
||||
|
||||
if (DBA::isResult($r)) {
|
||||
$nick = $r[0]["nick"];
|
||||
}
|
||||
}
|
||||
|
||||
if (!$nick == "") {
|
||||
$friendica = preg_replace("=https?://(.*)/profile/(.*)=ism", "$2", $profile);
|
||||
if ($friendica != $profile) {
|
||||
$nick = $friendica;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$nick == "") {
|
||||
$diaspora = preg_replace("=https?://(.*)/u/(.*)=ism", "$2", $profile);
|
||||
if ($diaspora != $profile) {
|
||||
$nick = $diaspora;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$nick == "") {
|
||||
$twitter = preg_replace("=https?://twitter.com/(.*)=ism", "$1", $profile);
|
||||
if ($twitter != $profile) {
|
||||
$nick = $twitter;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!$nick == "") {
|
||||
$StatusnetHost = preg_replace("=https?://(.*)/user/(.*)=ism", "$1", $profile);
|
||||
if ($StatusnetHost != $profile) {
|
||||
$StatusnetUser = preg_replace("=https?://(.*)/user/(.*)=ism", "$2", $profile);
|
||||
if ($StatusnetUser != $profile) {
|
||||
$UserData = Network::fetchUrl("http://".$StatusnetHost."/api/users/show.json?user_id=".$StatusnetUser);
|
||||
$user = json_decode($UserData);
|
||||
if ($user) {
|
||||
$nick = $user->screen_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// To-Do: look at the page if its really a pumpio site
|
||||
//if (!$nick == "") {
|
||||
// $pumpio = preg_replace("=https?://(.*)/(.*)/=ism", "$2", $profile."/");
|
||||
// if ($pumpio != $profile)
|
||||
// $nick = $pumpio;
|
||||
// <div class="media" id="profile-block" data-profile-id="acct:kabniel@microca.st">
|
||||
|
||||
//}
|
||||
|
||||
if ($nick != "") {
|
||||
return $nick;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param array $item
|
||||
@@ -5303,10 +5264,6 @@ function api_in_reply_to($item)
|
||||
$parent = Item::selectFirst($fields, ['id' => $in_reply_to['status_id']]);
|
||||
|
||||
if (DBA::isResult($parent)) {
|
||||
if ($parent['author-nick'] == "") {
|
||||
$parent['author-nick'] = api_get_nick($parent['author-link']);
|
||||
}
|
||||
|
||||
$in_reply_to['screen_name'] = (($parent['author-nick']) ? $parent['author-nick'] : $parent['author-name']);
|
||||
$in_reply_to['user_id'] = intval($parent['author-id']);
|
||||
$in_reply_to['user_id_str'] = (string) intval($parent['author-id']);
|
||||
|
||||
+5
-3
@@ -11,6 +11,7 @@ use Friendica\Core\Logger;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Protocol\Activity;
|
||||
@@ -775,17 +776,18 @@ function check_item_notification($itemid, $uid, $defaulttype = "") {
|
||||
|
||||
if ($item["parent-uri"] === $item["uri"]) {
|
||||
// Send a notification for every new post?
|
||||
// Either the contact had posted something directly
|
||||
$send_notification = DBA::exists('contact', ['id' => $item['contact-id'], 'notify_new_posts' => true]);
|
||||
|
||||
// Or the contact is a mentioned forum
|
||||
if (!$send_notification) {
|
||||
$tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
|
||||
intval(TERM_OBJ_POST), intval($itemid), intval(TERM_MENTION), intval($uid));
|
||||
|
||||
if (DBA::isResult($tags)) {
|
||||
foreach ($tags AS $tag) {
|
||||
$condition = ['nurl' => Strings::normaliseLink($tag["url"]), 'uid' => $uid, 'notify_new_posts' => true];
|
||||
$r = DBA::exists('contact', $condition);
|
||||
if ($r) {
|
||||
$condition = ['nurl' => Strings::normaliseLink($tag["url"]), 'uid' => $uid, 'notify_new_posts' => true, 'contact-type' => Contact::TYPE_COMMUNITY];
|
||||
if (DBA::exists('contact', $condition)) {
|
||||
$send_notification = true;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -136,7 +136,7 @@ function common_content(App $a)
|
||||
$title = '';
|
||||
$tab_str = '';
|
||||
if ($cmd === 'loc' && $cid && local_user() == $uid) {
|
||||
$tab_str = Module\Contact::getTabsHTML($a, $contact, 4);
|
||||
$tab_str = Module\Contact::getTabsHTML($a, $contact, 5);
|
||||
} else {
|
||||
$title = L10n::t('Common Friends');
|
||||
}
|
||||
|
||||
+1
-1
@@ -134,7 +134,7 @@ function crepair_content(App $a)
|
||||
|
||||
$update_profile = in_array($contact['network'], Protocol::FEDERATED);
|
||||
|
||||
$tab_str = Module\Contact::getTabsHTML($a, $contact, 5);
|
||||
$tab_str = Module\Contact::getTabsHTML($a, $contact, 6);
|
||||
|
||||
$tpl = Renderer::getMarkupTemplate('crepair.tpl');
|
||||
$o = Renderer::replaceMacros($tpl, [
|
||||
|
||||
+17
-10
@@ -93,7 +93,8 @@ function display_init(App $a)
|
||||
}
|
||||
|
||||
if ($item["id"] != $item["parent"]) {
|
||||
$item = Item::selectFirstForUser($item_user, $fields, ['id' => $item["parent"]]);
|
||||
$parent = Item::selectFirstForUser($item_user, $fields, ['id' => $item["parent"]]);
|
||||
$item = $parent ?: $item;
|
||||
}
|
||||
|
||||
$profiledata = display_fetchauthor($a, $item);
|
||||
@@ -171,6 +172,8 @@ function display_content(App $a, $update = false, $update_uid = 0)
|
||||
|
||||
$o = '';
|
||||
|
||||
$item = null;
|
||||
|
||||
if ($update) {
|
||||
$item_id = $_REQUEST['item_id'];
|
||||
$item = Item::selectFirst(['uid', 'parent', 'parent-uri'], ['id' => $item_id]);
|
||||
@@ -220,7 +223,7 @@ function display_content(App $a, $update = false, $update_uid = 0)
|
||||
}
|
||||
}
|
||||
|
||||
if (!$item_id) {
|
||||
if (empty($item)) {
|
||||
throw new HTTPException\NotFoundException(L10n::t('The requested item doesn\'t exist or has been deleted.'));
|
||||
}
|
||||
|
||||
@@ -242,16 +245,20 @@ function display_content(App $a, $update = false, $update_uid = 0)
|
||||
$is_remote_contact = false;
|
||||
$item_uid = local_user();
|
||||
|
||||
if (isset($item_parent_uri)) {
|
||||
$parent = null;
|
||||
if (!empty($item_parent_uri)) {
|
||||
$parent = Item::selectFirst(['uid'], ['uri' => $item_parent_uri, 'wall' => true]);
|
||||
if (DBA::isResult($parent)) {
|
||||
$a->profile['uid'] = ($a->profile['uid'] ?? 0) ?: $parent['uid'];
|
||||
$a->profile['profile_uid'] = ($a->profile['profile_uid'] ?? 0) ?: $parent['uid'];
|
||||
$is_remote_contact = Session::getRemoteContactID($a->profile['profile_uid']);
|
||||
if ($is_remote_contact) {
|
||||
$item_uid = $parent['uid'];
|
||||
}
|
||||
}
|
||||
|
||||
if (DBA::isResult($parent)) {
|
||||
$a->profile['uid'] = ($a->profile['uid'] ?? 0) ?: $parent['uid'];
|
||||
$a->profile['profile_uid'] = ($a->profile['profile_uid'] ?? 0) ?: $parent['uid'];
|
||||
$is_remote_contact = Session::getRemoteContactID($a->profile['profile_uid']);
|
||||
if ($is_remote_contact) {
|
||||
$item_uid = $parent['uid'];
|
||||
}
|
||||
} else {
|
||||
$a->profile = ['uid' => intval($item['uid']), 'profile_uid' => intval($item['uid'])];
|
||||
}
|
||||
|
||||
$page_contact = DBA::selectFirst('contact', [], ['self' => true, 'uid' => $a->profile['uid']]);
|
||||
|
||||
+12
-35
@@ -14,7 +14,7 @@ use Friendica\Core\Renderer;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Module\Login;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Introduction;
|
||||
use Friendica\Model\Notify;
|
||||
|
||||
function notifications_post(App $a)
|
||||
@@ -30,43 +30,20 @@ function notifications_post(App $a)
|
||||
}
|
||||
|
||||
if ($request_id) {
|
||||
$intro = DBA::selectFirst('intro', ['id', 'contact-id', 'fid'], ['id' => $request_id, 'uid' => local_user()]);
|
||||
/** @var Introduction $Intro */
|
||||
$Intro = \Friendica\BaseObject::getClass(Introduction::class);
|
||||
$Intro->fetch(['id' => $request_id, 'uid' => local_user()]);
|
||||
|
||||
if (DBA::isResult($intro)) {
|
||||
$intro_id = $intro['id'];
|
||||
$contact_id = $intro['contact-id'];
|
||||
} else {
|
||||
notice(L10n::t('Invalid request identifier.') . EOL);
|
||||
return;
|
||||
switch ($_POST['submit']) {
|
||||
case L10n::t('Discard'):
|
||||
$Intro->discard();
|
||||
break;
|
||||
case L10n::t('Ignore'):
|
||||
$Intro->ignore();
|
||||
break;
|
||||
}
|
||||
|
||||
// If it is a friend suggestion, the contact is not a new friend but an existing friend
|
||||
// that should not be deleted.
|
||||
|
||||
$fid = $intro['fid'];
|
||||
|
||||
if ($_POST['submit'] == L10n::t('Discard')) {
|
||||
DBA::delete('intro', ['id' => $intro_id]);
|
||||
if (!$fid) {
|
||||
// When the contact entry had been created just for that intro, we want to get rid of it now
|
||||
$condition = ['id' => $contact_id, 'uid' => local_user(),
|
||||
'self' => false, 'pending' => true, 'rel' => [0, Contact::FOLLOWER]];
|
||||
$contact_pending = DBA::exists('contact', $condition);
|
||||
|
||||
// Remove the "pending" to stop the reappearing in any case
|
||||
DBA::update('contact', ['pending' => false], ['id' => $contact_id]);
|
||||
|
||||
if ($contact_pending) {
|
||||
Contact::remove($contact_id);
|
||||
}
|
||||
}
|
||||
$a->internalRedirect('notifications/intros');
|
||||
}
|
||||
|
||||
if ($_POST['submit'] == L10n::t('Ignore')) {
|
||||
DBA::update('intro', ['ignore' => true], ['id' => $intro_id]);
|
||||
$a->internalRedirect('notifications/intros');
|
||||
}
|
||||
$a->internalRedirect('notifications/intros');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Friendica\Api\Mastodon;
|
||||
|
||||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
||||
/**
|
||||
@@ -55,31 +56,33 @@ class Account
|
||||
/**
|
||||
* Creates an account record from a contact record. Expects all contact table fields to be set
|
||||
*
|
||||
* @param array $contact
|
||||
* @param array $contact Full contact table record
|
||||
* @param array $apcontact Full apcontact table record
|
||||
* @return Account
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function createFromContact(array $contact) {
|
||||
public static function createFromContact(array $contact, array $apcontact = [])
|
||||
{
|
||||
$account = new Account();
|
||||
$account->id = $contact['id'];
|
||||
$account->username = $contact['nick'];
|
||||
$account->acct = $contact['nick'];
|
||||
$account->display_name = $contact['name'];
|
||||
$account->locked = $contact['blocked'];
|
||||
$account->created_at = DateTimeFormat::utc($contact['created'], DateTimeFormat::ATOM);
|
||||
// No data is available from contact
|
||||
$account->followers_count = 0;
|
||||
$account->following_count = 0;
|
||||
$account->statuses_count = 0;
|
||||
$account->note = BBCode::convert($contact['about']);
|
||||
$account->url = $contact['url'];
|
||||
$account->avatar = $contact['avatar'];
|
||||
$account->avatar_static = $contact['avatar'];
|
||||
$account->id = $contact['id'];
|
||||
$account->username = $contact['nick'];
|
||||
$account->acct = $contact['nick'];
|
||||
$account->display_name = $contact['name'];
|
||||
$account->locked = !empty($apcontact['manually-approve']);
|
||||
$account->created_at = DateTimeFormat::utc($contact['created'], DateTimeFormat::ATOM);
|
||||
$account->followers_count = $apcontact['followers_count'] ?? 0;
|
||||
$account->following_count = $apcontact['following_count'] ?? 0;
|
||||
$account->statuses_count = $apcontact['statuses_count'] ?? 0;
|
||||
$account->note = BBCode::convert($contact['about'], false);
|
||||
$account->url = $contact['url'];
|
||||
$account->avatar = $contact['avatar'];
|
||||
$account->avatar_static = $contact['avatar'];
|
||||
// No header picture in Friendica
|
||||
$account->header = '';
|
||||
$account->header_static = '';
|
||||
$account->header = '';
|
||||
$account->header_static = '';
|
||||
// No custom emojis per account in Friendica
|
||||
$account->emojis = [];
|
||||
$account->emojis = [];
|
||||
$account->bot = ($contact['contact-type'] == Contact::TYPE_NEWS);
|
||||
|
||||
return $account;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Friendica\Api\Mastodon;
|
||||
|
||||
use Friendica\App;
|
||||
use Friendica\Api\Mastodon\Account;
|
||||
use Friendica\Api\Mastodon\Stats;
|
||||
use Friendica\Core\Config;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Model\APContact;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Module\Register;
|
||||
|
||||
/**
|
||||
* Class Instance
|
||||
*
|
||||
* @see https://docs.joinmastodon.org/api/entities/#instance
|
||||
*/
|
||||
class Instance
|
||||
{
|
||||
/** @var string (URL) */
|
||||
var $uri;
|
||||
/** @var string */
|
||||
var $title;
|
||||
/** @var string */
|
||||
var $description;
|
||||
/** @var string */
|
||||
var $email;
|
||||
/** @var string */
|
||||
var $version;
|
||||
/** @var array */
|
||||
var $urls;
|
||||
/** @var Stats */
|
||||
var $stats;
|
||||
/** @var string */
|
||||
var $thumbnail;
|
||||
/** @var array */
|
||||
var $languages;
|
||||
/** @var int */
|
||||
var $max_toot_chars;
|
||||
/** @var bool */
|
||||
var $registrations;
|
||||
/** @var bool */
|
||||
var $approval_required;
|
||||
/** @var Account|null */
|
||||
var $contact_account;
|
||||
|
||||
/**
|
||||
* Creates an instance record
|
||||
*
|
||||
* @param App $app
|
||||
*
|
||||
* @return Instance
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function get(App $app) {
|
||||
$register_policy = intval(Config::get('config', 'register_policy'));
|
||||
|
||||
$instance = new Instance();
|
||||
$instance->uri = $app->getBaseURL();
|
||||
$instance->title = Config::get('config', 'sitename');
|
||||
$instance->description = Config::get('config', 'info');
|
||||
$instance->email = Config::get('config', 'admin_email');
|
||||
$instance->version = FRIENDICA_VERSION;
|
||||
$instance->urls = []; // Not supported
|
||||
$instance->stats = Stats::get();
|
||||
$instance->thumbnail = $app->getBaseURL() . (Config::get('system', 'shortcut_icon') ?? 'images/friendica-32.png');
|
||||
$instance->languages = [Config::get('system', 'language')];
|
||||
$instance->max_toot_chars = (int)Config::get('config', 'api_import_size', Config::get('config', 'max_import_size'));
|
||||
$instance->registrations = ($register_policy != Register::CLOSED);
|
||||
$instance->approval_required = ($register_policy == Register::APPROVE);
|
||||
$instance->contact_account = [];
|
||||
|
||||
if (!empty(Config::get('config', 'admin_email'))) {
|
||||
$adminList = explode(',', str_replace(' ', '', Config::get('config', 'admin_email')));
|
||||
$administrator = User::getByEmail($adminList[0], ['nickname']);
|
||||
if (!empty($administrator)) {
|
||||
$adminContact = DBA::selectFirst('contact', [], ['nick' => $administrator['nickname'], 'self' => true]);
|
||||
$apcontact = APContact::getByURL($adminContact['url'], false);
|
||||
$instance->contact_account = Account::createFromContact($adminContact, $apcontact);
|
||||
}
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Friendica\Api\Mastodon;
|
||||
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Util\Network;
|
||||
|
||||
/**
|
||||
* Class Relationship
|
||||
*
|
||||
* @see https://docs.joinmastodon.org/api/entities/#relationship
|
||||
*/
|
||||
class Relationship
|
||||
{
|
||||
/** @var int */
|
||||
var $id;
|
||||
/** @var bool */
|
||||
var $following = false;
|
||||
/** @var bool */
|
||||
var $followed_by = false;
|
||||
/** @var bool */
|
||||
var $blocking = false;
|
||||
/** @var bool */
|
||||
var $muting = false;
|
||||
/** @var bool */
|
||||
var $muting_notifications = false;
|
||||
/** @var bool */
|
||||
var $requested = false;
|
||||
/** @var bool */
|
||||
var $domain_blocking = false;
|
||||
/** @var bool */
|
||||
var $showing_reblogs = false;
|
||||
/** @var bool */
|
||||
var $endorsed = false;
|
||||
|
||||
/**
|
||||
* @param array $contact Full Contact table record
|
||||
* @return Relationship
|
||||
*/
|
||||
public static function createFromContact(array $contact)
|
||||
{
|
||||
$relationship = new self();
|
||||
|
||||
$relationship->id = $contact['id'];
|
||||
$relationship->following = in_array($contact['rel'], [Contact::SHARING, Contact::FRIEND]);
|
||||
$relationship->followed_by = in_array($contact['rel'], [Contact::FOLLOWER, Contact::FRIEND]);
|
||||
$relationship->blocking = (bool)$contact['blocked'];
|
||||
$relationship->muting = (bool)$contact['readonly'];
|
||||
$relationship->muting_notifications = (bool)$contact['readonly'];
|
||||
$relationship->requested = (bool)$contact['pending'];
|
||||
$relationship->domain_blocking = Network::isUrlBlocked($contact['url']);
|
||||
// Unsupported
|
||||
$relationship->showing_reblogs = true;
|
||||
// Unsupported
|
||||
$relationship->endorsed = false;
|
||||
|
||||
return $relationship;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Friendica\Api\Mastodon;
|
||||
|
||||
use Friendica\Core\Config;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Database\DBA;
|
||||
|
||||
/**
|
||||
* Class Stats
|
||||
*
|
||||
* @see https://docs.joinmastodon.org/api/entities/#stats
|
||||
*/
|
||||
class Stats
|
||||
{
|
||||
/** @var int */
|
||||
var $user_count;
|
||||
/** @var int */
|
||||
var $status_count;
|
||||
/** @var int */
|
||||
var $domain_count;
|
||||
|
||||
/**
|
||||
* Creates a stats record
|
||||
*
|
||||
* @return Stats
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function get() {
|
||||
$stats = new Stats();
|
||||
if (!empty(Config::get('system', 'nodeinfo'))) {
|
||||
$stats->user_count = intval(Config::get('nodeinfo', 'total_users'));
|
||||
$stats->status_count = Config::get('nodeinfo', 'local_posts') + Config::get('nodeinfo', 'local_comments');
|
||||
$stats->domain_count = DBA::count('gserver', ["`network` in (?, ?) AND `last_contact` >= `last_failure`", Protocol::DFRN, Protocol::ACTIVITYPUB]);
|
||||
}
|
||||
return $stats;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -385,7 +385,7 @@ class App
|
||||
* @deprecated 2019.09 - Use BaseURL->remove() instead
|
||||
* @see BaseURL::remove()
|
||||
*/
|
||||
public function removeBaseURL($origURL)
|
||||
public function removeBaseURL(string $origURL)
|
||||
{
|
||||
return $this->baseURL->remove($origURL);
|
||||
}
|
||||
|
||||
+4
-4
@@ -251,10 +251,6 @@ class Module
|
||||
|
||||
call_user_func([$this->module_class, 'init'], $this->module_parameters);
|
||||
|
||||
// "rawContent" is especially meant for technical endpoints.
|
||||
// This endpoint doesn't need any theme initialization or other comparable stuff.
|
||||
call_user_func([$this->module_class, 'rawContent'], $this->module_parameters);
|
||||
|
||||
if ($server['REQUEST_METHOD'] === 'POST') {
|
||||
Core\Hook::callAll($this->module . '_mod_post', $post);
|
||||
call_user_func([$this->module_class, 'post'], $this->module_parameters);
|
||||
@@ -262,5 +258,9 @@ class Module
|
||||
|
||||
Core\Hook::callAll($this->module . '_mod_afterpost', $placeholder);
|
||||
call_user_func([$this->module_class, 'afterpost'], $this->module_parameters);
|
||||
|
||||
// "rawContent" is especially meant for technical endpoints.
|
||||
// This endpoint doesn't need any theme initialization or other comparable stuff.
|
||||
call_user_func([$this->module_class, 'rawContent'], $this->module_parameters);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace Friendica;
|
||||
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Class BaseModel
|
||||
*
|
||||
* The Model classes inheriting from this abstract class are meant to represent a single database record.
|
||||
* The associated table name has to be provided in the child class, and the table is expected to have a unique `id` field.
|
||||
*
|
||||
* @property int id
|
||||
*/
|
||||
abstract class BaseModel
|
||||
{
|
||||
protected static $table_name;
|
||||
|
||||
/** @var Database */
|
||||
protected $dba;
|
||||
/** @var LoggerInterface */
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* Model record abstraction.
|
||||
* Child classes never have to interact directly with it.
|
||||
* Please use the magic getter instead.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $data = [];
|
||||
|
||||
public function __construct(Database $dba, LoggerInterface $logger)
|
||||
{
|
||||
$this->dba = $dba;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic getter. This allows to retrieve model fields with the following syntax:
|
||||
* - $model->field (outside of class)
|
||||
* - $this->field (inside of class)
|
||||
*
|
||||
* @param $name
|
||||
* @return mixed
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
if (empty($this->data['id'])) {
|
||||
throw new HTTPException\InternalServerErrorException(static::class . ' record uninitialized');
|
||||
}
|
||||
|
||||
if (!array_key_exists($name, $this->data)) {
|
||||
throw new HTTPException\InternalServerErrorException('Field ' . $name . ' not found in ' . static::class);
|
||||
}
|
||||
|
||||
return $this->data[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a single model record. The condition array is expected to contain a unique index (primary or otherwise).
|
||||
*
|
||||
* Chainable.
|
||||
*
|
||||
* @param array $condition
|
||||
* @return BaseModel
|
||||
* @throws HTTPException\NotFoundException
|
||||
*/
|
||||
public function fetch(array $condition)
|
||||
{
|
||||
$intro = $this->dba->selectFirst(static::$table_name, [], $condition);
|
||||
|
||||
if (!$intro) {
|
||||
throw new HTTPException\NotFoundException(static::class . ' record not found.');
|
||||
}
|
||||
|
||||
$this->data = $intro;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the model record from the database.
|
||||
* Prevents further methods from being called by wiping the internal model data.
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
if ($this->dba->delete(static::$table_name, ['id' => $this->id])) {
|
||||
$this->data = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -354,6 +354,9 @@ class BBCode extends BaseObject
|
||||
$post['url'] = $links[0][1];
|
||||
}
|
||||
|
||||
// Simplify "video" element
|
||||
$post['text'] = preg_replace('(\[video.*?\ssrc\s?=\s?([^\s\]]+).*?\].*?\[/video\])ism', '[video]$1[/video]', $post['text']);
|
||||
|
||||
// Now count the number of external media links
|
||||
preg_match_all("(\[vimeo\](.*?)\[\/vimeo\])ism", $post['text'], $links1, PREG_SET_ORDER);
|
||||
preg_match_all("(\[youtube\\](.*?)\[\/youtube\\])ism", $post['text'], $links2, PREG_SET_ORDER);
|
||||
@@ -395,15 +398,15 @@ class BBCode extends BaseObject
|
||||
*/
|
||||
public static function removeAttachment($body, $no_link_desc = false)
|
||||
{
|
||||
return preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
|
||||
return preg_replace_callback("/\s*\[attachment (.*)\](.*?)\[\/attachment\]\s*/ism",
|
||||
function ($match) use ($no_link_desc) {
|
||||
$attach_data = self::getAttachmentData($match[0]);
|
||||
if (empty($attach_data['url'])) {
|
||||
return $match[0];
|
||||
} elseif (empty($attach_data['title']) || $no_link_desc) {
|
||||
return '[url]' . $attach_data['url'] . "[/url]\n";
|
||||
return "\n[url]" . $attach_data['url'] . "[/url]\n";
|
||||
} else {
|
||||
return '[url=' . $attach_data['url'] . ']' . $attach_data['title'] . "[/url]\n";
|
||||
return "\n[url=" . $attach_data['url'] . ']' . $attach_data['title'] . "[/url]\n";
|
||||
}
|
||||
}, $body);
|
||||
}
|
||||
@@ -1504,8 +1507,29 @@ class BBCode extends BaseObject
|
||||
$text = str_replace('[hr]', '<hr />', $text);
|
||||
|
||||
if (!$for_plaintext) {
|
||||
$escaped = [];
|
||||
|
||||
// Escaping BBCodes susceptible to contain rogue URL we don'' want the autolinker to catch
|
||||
$text = preg_replace_callback('#\[(url|img|audio|video|youtube|vimeo|share|attachment|iframe|bookmark).+?\[/\1\]#ism',
|
||||
function ($matches) use (&$escaped) {
|
||||
$return = '{escaped-' . count($escaped) . '}';
|
||||
$escaped[] = $matches[0];
|
||||
|
||||
return $return;
|
||||
},
|
||||
$text
|
||||
);
|
||||
|
||||
// Autolinker for isolated URLs
|
||||
$text = preg_replace(Strings::autoLinkRegEx(), '[url]$1[/url]', $text);
|
||||
|
||||
// Restoring escaped blocks
|
||||
$text = preg_replace_callback('/{escaped-([0-9]+)}/iU',
|
||||
function ($matches) use ($escaped) {
|
||||
return $escaped[intval($matches[1])] ?? $matches[0];
|
||||
},
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
// This is actually executed in Item::prepareBody()
|
||||
@@ -1606,6 +1630,9 @@ class BBCode extends BaseObject
|
||||
$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);
|
||||
|
||||
// Simplify "video" element
|
||||
$text = preg_replace('(\[video.*?\ssrc\s?=\s?([^\s\]]+).*?\].*?\[/video\])ism', '[video]$1[/video]', $text);
|
||||
|
||||
// Try to Oembed
|
||||
if ($try_oembed) {
|
||||
$text = preg_replace("/\[video\](.*?\.(ogg|ogv|oga|ogm|webm|mp4).*?)\[\/video\]/ism", '<video src="$1" controls="controls" width="' . $a->videowidth . '" height="' . $a->videoheight . '" loop="true"><a href="$1">$1</a></video>', $text);
|
||||
|
||||
@@ -335,6 +335,10 @@ class ACL extends BaseObject
|
||||
*/
|
||||
public static function getFullSelectorHTML(Page $page, array $user = null, bool $for_federation = false, array $default_permissions = [])
|
||||
{
|
||||
if (empty($user['uid'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$page->registerFooterScript(Theme::getPathForFile('asset/typeahead.js/dist/typeahead.bundle.js'));
|
||||
$page->registerFooterScript(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.js'));
|
||||
$page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.css'));
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ class System extends BaseObject
|
||||
* @return string The cleaned url
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function removedBaseUrl($orig_url)
|
||||
public static function removedBaseUrl(string $orig_url)
|
||||
{
|
||||
return self::getApp()->removeBaseURL($orig_url);
|
||||
}
|
||||
|
||||
@@ -334,9 +334,9 @@ class DBA extends BaseObject
|
||||
/**
|
||||
* @brief Delete a row from a table
|
||||
*
|
||||
* @param string $table Table name
|
||||
* @param array $conditions Field condition(s)
|
||||
* @param array $options
|
||||
* @param string|array $table Table name
|
||||
* @param array $conditions Field condition(s)
|
||||
* @param array $options
|
||||
* - cascade: If true we delete records in other tables that depend on the one we're deleting through
|
||||
* relations (default: true)
|
||||
*
|
||||
@@ -411,7 +411,7 @@ class DBA extends BaseObject
|
||||
* @throws \Exception
|
||||
* @see self::select
|
||||
*/
|
||||
public static function selectToArray(string $table, array $fields = [], array $condition = [], array $params = [])
|
||||
public static function selectToArray($table, array $fields = [], array $condition = [], array $params = [])
|
||||
{
|
||||
return self::getClass(Database::class)->selectToArray($table, $fields, $condition, $params);
|
||||
}
|
||||
|
||||
@@ -1377,10 +1377,10 @@ class Database
|
||||
*
|
||||
* @brief Retrieve a single record from a table
|
||||
*
|
||||
* @param string $table
|
||||
* @param array $fields
|
||||
* @param array $condition
|
||||
* @param array $params
|
||||
* @param string|array $table
|
||||
* @param array $fields
|
||||
* @param array $condition
|
||||
* @param array $params
|
||||
*
|
||||
* @return bool|array
|
||||
* @throws \Exception
|
||||
@@ -1412,7 +1412,7 @@ class Database
|
||||
* @throws \Exception
|
||||
* @see self::select
|
||||
*/
|
||||
public function selectToArray(string $table, array $fields = [], array $condition = [], array $params = [])
|
||||
public function selectToArray($table, array $fields = [], array $condition = [], array $params = [])
|
||||
{
|
||||
return $this->toArray($this->select($table, $fields, $condition, $params));
|
||||
}
|
||||
|
||||
+29
-2
@@ -84,7 +84,7 @@ class APContact extends BaseObject
|
||||
public static function getByURL($url, $update = null)
|
||||
{
|
||||
if (empty($url)) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
$fetched_contact = false;
|
||||
@@ -110,7 +110,7 @@ class APContact extends BaseObject
|
||||
}
|
||||
|
||||
if (!is_null($update)) {
|
||||
return DBA::isResult($apcontact) ? $apcontact : false;
|
||||
return DBA::isResult($apcontact) ? $apcontact : [];
|
||||
}
|
||||
|
||||
if (DBA::isResult($apcontact)) {
|
||||
@@ -203,6 +203,33 @@ class APContact extends BaseObject
|
||||
$apcontact['generator'] = JsonLD::fetchElement($compacted['as:generator'], 'as:name', '@value');
|
||||
}
|
||||
|
||||
if (!empty($apcontact['following'])) {
|
||||
$data = ActivityPub::fetchContent($apcontact['following']);
|
||||
if (!empty($data)) {
|
||||
if (!empty($data['totalItems'])) {
|
||||
$apcontact['following_count'] = $data['totalItems'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($apcontact['followers'])) {
|
||||
$data = ActivityPub::fetchContent($apcontact['followers']);
|
||||
if (!empty($data)) {
|
||||
if (!empty($data['totalItems'])) {
|
||||
$apcontact['followers_count'] = $data['totalItems'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($apcontact['outbox'])) {
|
||||
$data = ActivityPub::fetchContent($apcontact['outbox']);
|
||||
if (!empty($data)) {
|
||||
if (!empty($data['totalItems'])) {
|
||||
$apcontact['statuses_count'] = $data['totalItems'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// To-Do
|
||||
|
||||
// Unhandled
|
||||
|
||||
@@ -1230,7 +1230,7 @@ class Contact extends BaseObject
|
||||
|
||||
$follow_link = '';
|
||||
$unfollow_link = '';
|
||||
if (in_array($contact['network'], Protocol::NATIVE_SUPPORT)) {
|
||||
if (!$contact['self'] && in_array($contact['network'], Protocol::NATIVE_SUPPORT)) {
|
||||
if ($contact['uid'] && in_array($contact['rel'], [self::SHARING, self::FRIEND])) {
|
||||
$unfollow_link = 'unfollow?url=' . urlencode($contact['url']);
|
||||
} elseif(!$contact['pending']) {
|
||||
|
||||
@@ -876,7 +876,11 @@ class GContact
|
||||
self::updateFromOutbox($outbox['first']['href'], $data);
|
||||
return;
|
||||
} elseif (!empty($outbox['first'])) {
|
||||
self::updateFromOutbox($outbox['first'], $data);
|
||||
if (is_string($outbox['first'])) {
|
||||
self::updateFromOutbox($outbox['first'], $data);
|
||||
} else {
|
||||
Logger::warning('Unexpected data', ['outbox' => $outbox]);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
$items = [];
|
||||
|
||||
@@ -549,7 +549,7 @@ class GServer
|
||||
$protocols[$protocol] = true;
|
||||
}
|
||||
|
||||
if (!empty($protocols['friendica'])) {
|
||||
if (!empty($protocols['dfrn'])) {
|
||||
$server['network'] = Protocol::DFRN;
|
||||
} elseif (!empty($protocols['activitypub'])) {
|
||||
$server['network'] = Protocol::ACTIVITYPUB;
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace Friendica\Model;
|
||||
|
||||
use Friendica\BaseModel;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Protocol\Diaspora;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
||||
/**
|
||||
* @property int uid
|
||||
* @property int fid
|
||||
* @property int contact-id
|
||||
* @property bool knowyou
|
||||
* @property bool duplex
|
||||
* @property string note
|
||||
* @property string hash
|
||||
* @property string datetime
|
||||
* @property bool blocked
|
||||
* @property bool ignored
|
||||
*
|
||||
* @package Friendica\Model
|
||||
*/
|
||||
final class Introduction extends BaseModel
|
||||
{
|
||||
static $table_name = 'intro';
|
||||
|
||||
/**
|
||||
* Confirms a follow request and sends a notic to the remote contact.
|
||||
*
|
||||
* @param bool $duplex Is it a follow back?
|
||||
* @param bool|null $hidden Should this contact be hidden? null = no change
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
* @throws HTTPException\NotFoundException
|
||||
*/
|
||||
public function confirm(bool $duplex = false, bool $hidden = null)
|
||||
{
|
||||
$this->logger->info('Confirming follower', ['cid' => $this->{'contact-id'}]);
|
||||
|
||||
$contact = Contact::selectFirst([], ['id' => $this->{'contact-id'}, 'uid' => $this->uid]);
|
||||
|
||||
if (!$contact) {
|
||||
throw new HTTPException\NotFoundException('Contact record not found.');
|
||||
}
|
||||
|
||||
$new_relation = $contact['rel'];
|
||||
$writable = $contact['writable'];
|
||||
|
||||
if (!empty($contact['protocol'])) {
|
||||
$protocol = $contact['protocol'];
|
||||
} else {
|
||||
$protocol = $contact['network'];
|
||||
}
|
||||
|
||||
if ($protocol == Protocol::ACTIVITYPUB) {
|
||||
ActivityPub\Transmitter::sendContactAccept($contact['url'], $contact['hub-verify'], $contact['uid']);
|
||||
}
|
||||
|
||||
if (in_array($protocol, [Protocol::DIASPORA, Protocol::ACTIVITYPUB])) {
|
||||
if ($duplex) {
|
||||
$new_relation = Contact::FRIEND;
|
||||
} else {
|
||||
$new_relation = Contact::FOLLOWER;
|
||||
}
|
||||
|
||||
if ($new_relation != Contact::FOLLOWER) {
|
||||
$writable = 1;
|
||||
}
|
||||
}
|
||||
|
||||
$fields = [
|
||||
'name-date' => DateTimeFormat::utcNow(),
|
||||
'uri-date' => DateTimeFormat::utcNow(),
|
||||
'blocked' => false,
|
||||
'pending' => false,
|
||||
'protocol' => $protocol,
|
||||
'writable' => $writable,
|
||||
'hidden' => $hidden ?? $contact['hidden'],
|
||||
'rel' => $new_relation,
|
||||
];
|
||||
$this->dba->update('contact', $fields, ['id' => $contact['id']]);
|
||||
|
||||
array_merge($contact, $fields);
|
||||
|
||||
if ($new_relation == Contact::FRIEND) {
|
||||
if ($protocol == Protocol::DIASPORA) {
|
||||
$ret = Diaspora::sendShare(User::getById($contact['uid']), $contact);
|
||||
$this->logger->info('share returns', ['return' => $ret]);
|
||||
} elseif ($protocol == Protocol::ACTIVITYPUB) {
|
||||
ActivityPub\Transmitter::sendActivity('Follow', $contact['url'], $contact['uid']);
|
||||
}
|
||||
}
|
||||
|
||||
$this->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Silently ignores the introduction, hides it from notifications and prevents the remote contact from submitting
|
||||
* additional follow requests.
|
||||
*
|
||||
* Chainable
|
||||
*
|
||||
* @return Introduction
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function ignore()
|
||||
{
|
||||
$this->dba->update('intro', ['ignore' => true], ['id' => $this->id]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discards the introduction and sends a rejection message to AP contacts.
|
||||
*
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
* @throws HTTPException\NotFoundException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public function discard()
|
||||
{
|
||||
// If it is a friend suggestion, the contact is not a new friend but an existing friend
|
||||
// that should not be deleted.
|
||||
if (!$this->fid) {
|
||||
// When the contact entry had been created just for that intro, we want to get rid of it now
|
||||
$condition = ['id' => $this->{'contact-id'}, 'uid' => $this->uid,
|
||||
'self' => false, 'pending' => true, 'rel' => [0, Contact::FOLLOWER]];
|
||||
if ($this->dba->exists('contact', $condition)) {
|
||||
Contact::remove($this->{'contact-id'});
|
||||
} else {
|
||||
$this->dba->update('contact', ['pending' => false], ['id' => $this->{'contact-id'}]);
|
||||
}
|
||||
}
|
||||
|
||||
$contact = Contact::selectFirst([], ['id' => $this->{'contact-id'}, 'uid' => $this->uid]);
|
||||
|
||||
if (!$contact) {
|
||||
throw new HTTPException\NotFoundException('Contact record not found.');
|
||||
}
|
||||
|
||||
if (!empty($contact['protocol'])) {
|
||||
$protocol = $contact['protocol'];
|
||||
} else {
|
||||
$protocol = $contact['network'];
|
||||
}
|
||||
|
||||
if ($protocol == Protocol::ACTIVITYPUB) {
|
||||
ActivityPub\Transmitter::sendContactReject($contact['url'], $contact['hub-verify'], $contact['uid']);
|
||||
}
|
||||
|
||||
$this->delete();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -2621,7 +2621,7 @@ class Item extends BaseObject
|
||||
"#$2", $item["body"]);
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
if ((strpos($tag, '#') !== 0) || strpos($tag, '[url=') || $tag[1] == '#') {
|
||||
if ((strpos($tag, '#') !== 0) || strpos($tag, '[url=') || strlen($tag) < 2 || $tag[1] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
namespace Friendica\Module\Api\Mastodon;
|
||||
|
||||
use Friendica\Api\Mastodon\Account;
|
||||
use Friendica\Api\Mastodon;
|
||||
use Friendica\App\BaseURL;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Model\APContact;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Introduction;
|
||||
use Friendica\Module\Base\Api;
|
||||
use Friendica\Network\HTTPException;
|
||||
|
||||
@@ -19,7 +21,40 @@ class FollowRequests extends Api
|
||||
{
|
||||
parent::init($parameters);
|
||||
|
||||
self::login();
|
||||
if (!self::login()) {
|
||||
throw new HTTPException\UnauthorizedException();
|
||||
}
|
||||
}
|
||||
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
parent::post($parameters);
|
||||
|
||||
/** @var Introduction $Intro */
|
||||
$Intro = self::getClass(Introduction::class);
|
||||
$Intro->fetch(['id' => $parameters['id'], 'uid' => self::$current_user_id]);
|
||||
|
||||
$contactId = $Intro->{'contact-id'};
|
||||
|
||||
$relationship = new Mastodon\Relationship();
|
||||
$relationship->id = $contactId;
|
||||
|
||||
switch ($parameters['action']) {
|
||||
case 'authorize':
|
||||
$Intro->confirm();
|
||||
$relationship = Mastodon\Relationship::createFromContact(Contact::getById($contactId));
|
||||
break;
|
||||
case 'ignore':
|
||||
$Intro->ignore();
|
||||
break;
|
||||
case 'reject':
|
||||
$Intro->discard();
|
||||
break;
|
||||
default:
|
||||
throw new HTTPException\BadRequestException('Unexpected action parameter, expecting "authorize", "ignore" or "reject"');
|
||||
}
|
||||
|
||||
System::jsonExit($relationship);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,26 +69,32 @@ class FollowRequests extends Api
|
||||
$limit = intval($_GET['limit'] ?? 40);
|
||||
|
||||
if (isset($since_id) && isset($max_id)) {
|
||||
$condition = ['`uid` = ? AND NOT `self` AND `pending` AND `id` > ? AND `id` < ?', self::$current_user_id, $since_id, $max_id];
|
||||
$condition = ['`uid` = ? AND NOT `ignore` AND `id` > ? AND `id` < ?', self::$current_user_id, $since_id, $max_id];
|
||||
} elseif (isset($since_id)) {
|
||||
$condition = ['`uid` = ? AND NOT `self` AND `pending` AND `id` > ?', self::$current_user_id, $since_id];
|
||||
$condition = ['`uid` = ? AND NOT `ignore` AND `id` > ?', self::$current_user_id, $since_id];
|
||||
} elseif (isset($max_id)) {
|
||||
$condition = ['`uid` = ? AND NOT `self` AND `pending` AND `id` < ?', self::$current_user_id, $max_id];
|
||||
$condition = ['`uid` = ? AND NOT `ignore` AND `id` < ?', self::$current_user_id, $max_id];
|
||||
} else {
|
||||
$condition = ['`uid` = ? AND NOT `self` AND `pending`', self::$current_user_id];
|
||||
$condition = ['`uid` = ? AND NOT `ignore`', self::$current_user_id];
|
||||
}
|
||||
|
||||
$count = DBA::count('contact', $condition);
|
||||
$count = DBA::count('intro', $condition);
|
||||
|
||||
$contacts = Contact::selectToArray(
|
||||
$intros = DBA::selectToArray(
|
||||
'intro',
|
||||
[],
|
||||
$condition,
|
||||
['order' => ['id' => 'DESC'], 'limit' => $limit]
|
||||
);
|
||||
|
||||
$return = [];
|
||||
foreach ($contacts as $contact) {
|
||||
$account = Account::createFromContact($contact);
|
||||
foreach ($intros as $intro) {
|
||||
$contact = Contact::getById($intro['contact-id']);
|
||||
$apcontact = APContact::getByURL($contact['url'], false);
|
||||
$account = Mastodon\Account::createFromContact($contact, $apcontact);
|
||||
|
||||
// Not ideal, the same "account" can have multiple ids depending on the context
|
||||
$account->id = $intro['id'];
|
||||
|
||||
$return[] = $account;
|
||||
}
|
||||
@@ -68,9 +109,9 @@ class FollowRequests extends Api
|
||||
|
||||
$links = [];
|
||||
if ($count > $limit) {
|
||||
$links[] = '<' . $BaseURL->get() . '/api/v1/follow_requests?' . http_build_query($base_query + ['max_id' => $contacts[count($contacts) - 1]['id']]) . '>; rel="next"';
|
||||
$links[] = '<' . $BaseURL->get() . '/api/v1/follow_requests?' . http_build_query($base_query + ['max_id' => $intros[count($intros) - 1]['id']]) . '>; rel="next"';
|
||||
}
|
||||
$links[] = '<' . $BaseURL->get() . '/api/v1/follow_requests?' . http_build_query($base_query + ['since_id' => $contacts[0]['id']]) . '>; rel="prev"';
|
||||
$links[] = '<' . $BaseURL->get() . '/api/v1/follow_requests?' . http_build_query($base_query + ['since_id' => $intros[0]['id']]) . '>; rel="prev"';
|
||||
|
||||
header('Link: ' . implode(', ', $links));
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Friendica\Module\Api\Mastodon;
|
||||
|
||||
use Friendica\Api\Mastodon\Instance as InstanceEntity;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Module\Base\Api;
|
||||
|
||||
/**
|
||||
* @see https://docs.joinmastodon.org/api/rest/instances/
|
||||
*/
|
||||
class Instance extends Api
|
||||
{
|
||||
/**
|
||||
* @param array $parameters
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
System::jsonExit(InstanceEntity::get(self::getApp()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Friendica\Module\Api\Mastodon\Instance;
|
||||
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Module\Base\Api;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Util\Network;
|
||||
|
||||
/**
|
||||
* Undocumented API endpoint that is implemented by both Mastodon and Pleroma
|
||||
*/
|
||||
class Peers extends Api
|
||||
{
|
||||
/**
|
||||
* @param array $parameters
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
$return = [];
|
||||
|
||||
// We only select for Friendica and ActivityPub servers, since it is expected to only deliver AP compatible systems here.
|
||||
$instances = DBA::select('gserver', ['url'], ["`network` in (?, ?) AND `last_contact` >= `last_failure`", Protocol::DFRN, Protocol::ACTIVITYPUB]);
|
||||
while ($instance = DBA::fetch($instances)) {
|
||||
$urldata = parse_url($instance['url']);
|
||||
unset($urldata['scheme']);
|
||||
$return[] = ltrim(Network::unparseURL($urldata), '/');
|
||||
}
|
||||
DBA::close($instances);
|
||||
|
||||
System::jsonExit($return);
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,7 @@ class Api extends BaseModule
|
||||
*
|
||||
* @brief Login API user
|
||||
*
|
||||
* @return bool Was a user authenticated?
|
||||
* @throws HTTPException\ForbiddenException
|
||||
* @throws HTTPException\UnauthorizedException
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
@@ -69,6 +70,8 @@ class Api extends BaseModule
|
||||
api_login(self::getApp());
|
||||
|
||||
self::$current_user_id = api_user();
|
||||
|
||||
return (bool)self::$current_user_id;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+13
-9
@@ -646,21 +646,25 @@ class Contact extends BaseModule
|
||||
return $arr['output'];
|
||||
}
|
||||
|
||||
$select_uid = local_user();
|
||||
|
||||
// @TODO: Replace with parameter from router
|
||||
$type = $a->argv[1] ?? '';
|
||||
|
||||
switch ($type) {
|
||||
case 'blocked':
|
||||
$sql_extra = " AND `blocked`";
|
||||
$sql_extra = sprintf(" AND EXISTS(SELECT `id` from `user-contact` WHERE `contact`.`id` = `user-contact`.`cid` and `user-contact`.`uid` = %d and `user-contact`.`blocked`)", intval(local_user()));
|
||||
$select_uid = 0;
|
||||
break;
|
||||
case 'hidden':
|
||||
$sql_extra = " AND `hidden` AND NOT `blocked`";
|
||||
$sql_extra = " AND `hidden` AND NOT `blocked` AND NOT `pending`";
|
||||
break;
|
||||
case 'ignored':
|
||||
$sql_extra = " AND `readonly` AND NOT `blocked`";
|
||||
$sql_extra = sprintf(" AND EXISTS(SELECT `id` from `user-contact` WHERE `contact`.`id` = `user-contact`.`cid` and `user-contact`.`uid` = %d and `user-contact`.`ignored`)", intval(local_user()));
|
||||
$select_uid = 0;
|
||||
break;
|
||||
case 'archived':
|
||||
$sql_extra = " AND `archive` AND NOT `blocked`";
|
||||
$sql_extra = " AND `archive` AND NOT `blocked` AND NOT `pending`";
|
||||
break;
|
||||
case 'pending':
|
||||
$sql_extra = sprintf(" AND `pending` AND NOT `archive` AND ((`rel` = %d)
|
||||
@@ -762,21 +766,21 @@ class Contact extends BaseModule
|
||||
|
||||
$sql_extra2 = ((($sort_type > 0) && ($sort_type <= Model\Contact::FRIEND)) ? sprintf(" AND `rel` = %d ", intval($sort_type)) : '');
|
||||
|
||||
$sql_extra3 = Widget::unavailableNetworks();
|
||||
|
||||
$r = q("SELECT COUNT(*) AS `total` FROM `contact`
|
||||
WHERE `uid` = %d AND `self` = 0 $sql_extra $sql_extra2 ",
|
||||
intval($_SESSION['uid'])
|
||||
WHERE `uid` = %d AND `self` = 0 $sql_extra $sql_extra2 $sql_extra3",
|
||||
intval($select_uid)
|
||||
);
|
||||
if (DBA::isResult($r)) {
|
||||
$total = $r[0]['total'];
|
||||
}
|
||||
$pager = new Pager($a->query_string);
|
||||
|
||||
$sql_extra3 = Widget::unavailableNetworks();
|
||||
|
||||
$contacts = [];
|
||||
|
||||
$r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 0 $sql_extra $sql_extra2 $sql_extra3 ORDER BY `name` ASC LIMIT %d , %d ",
|
||||
intval($_SESSION['uid']),
|
||||
intval($select_uid),
|
||||
$pager->getStart(),
|
||||
$pager->getItemsPerPage()
|
||||
);
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
<?php
|
||||
namespace Friendica\Module;
|
||||
|
||||
use Friendica\App;
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Protocol\Diaspora;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Model\Introduction;
|
||||
|
||||
/**
|
||||
* Process follow request confirmations
|
||||
@@ -30,67 +22,15 @@ class FollowConfirm extends BaseModule
|
||||
|
||||
$intro_id = intval($_POST['intro_id'] ?? 0);
|
||||
$duplex = intval($_POST['duplex'] ?? 0);
|
||||
$cid = intval($_POST['contact_id'] ?? 0);
|
||||
$hidden = intval($_POST['hidden'] ?? 0);
|
||||
|
||||
if (empty($cid)) {
|
||||
notice(L10n::t('No given contact.') . EOL);
|
||||
return;
|
||||
}
|
||||
/** @var Introduction $Intro */
|
||||
$Intro = self::getClass(Introduction::class);
|
||||
$Intro->fetch(['id' => $intro_id, 'uid' => local_user()]);
|
||||
|
||||
Logger::info('Confirming follower', ['cid' => $cid]);
|
||||
$cid = $Intro->{'contact-id'};
|
||||
|
||||
$contact = DBA::selectFirst('contact', [], ['id' => $cid, 'uid' => $uid]);
|
||||
if (!DBA::isResult($contact)) {
|
||||
Logger::warning('Contact not found in DB.', ['cid' => $cid]);
|
||||
notice(L10n::t('Contact not found.') . EOL);
|
||||
return;
|
||||
}
|
||||
|
||||
$relation = $contact['rel'];
|
||||
$new_relation = $contact['rel'];
|
||||
$writable = $contact['writable'];
|
||||
|
||||
if (!empty($contact['protocol'])) {
|
||||
$protocol = $contact['protocol'];
|
||||
} else {
|
||||
$protocol = $contact['network'];
|
||||
}
|
||||
|
||||
if ($protocol == Protocol::ACTIVITYPUB) {
|
||||
ActivityPub\Transmitter::sendContactAccept($contact['url'], $contact['hub-verify'], $uid);
|
||||
}
|
||||
|
||||
if (in_array($protocol, [Protocol::DIASPORA, Protocol::ACTIVITYPUB])) {
|
||||
if ($duplex) {
|
||||
$new_relation = Contact::FRIEND;
|
||||
} else {
|
||||
$new_relation = Contact::FOLLOWER;
|
||||
}
|
||||
|
||||
if ($new_relation != Contact::FOLLOWER) {
|
||||
$writable = 1;
|
||||
}
|
||||
}
|
||||
|
||||
$fields = ['name-date' => DateTimeFormat::utcNow(),
|
||||
'uri-date' => DateTimeFormat::utcNow(),
|
||||
'blocked' => false, 'pending' => false, 'protocol' => $protocol,
|
||||
'writable' => $writable, 'hidden' => $hidden, 'rel' => $new_relation];
|
||||
DBA::update('contact', $fields, ['id' => $cid]);
|
||||
|
||||
if ($new_relation == Contact::FRIEND) {
|
||||
if ($protocol == Protocol::DIASPORA) {
|
||||
$user = User::getById($uid);
|
||||
$contact = Contact::getById($cid);
|
||||
$ret = Diaspora::sendShare($user, $contact);
|
||||
Logger::info('share returns', ['return' => $ret]);
|
||||
} elseif ($protocol == Protocol::ACTIVITYPUB) {
|
||||
ActivityPub\Transmitter::sendActivity('Follow', $contact['url'], $uid);
|
||||
}
|
||||
}
|
||||
|
||||
DBA::delete('intro', ['id' => $intro_id]);
|
||||
$Intro->confirm($duplex, $hidden);
|
||||
|
||||
$a->internalRedirect('contact/' . intval($cid));
|
||||
}
|
||||
|
||||
@@ -85,15 +85,20 @@ class Compose extends BaseModule
|
||||
$type = 'post';
|
||||
$doesFederate = true;
|
||||
|
||||
if ($_REQUEST['contact_allow']
|
||||
. $_REQUEST['group_allow']
|
||||
. $_REQUEST['contact_deny']
|
||||
. $_REQUEST['group_deny'])
|
||||
$contact_allow = $_REQUEST['contact_allow'] ?? '';
|
||||
$group_allow = $_REQUEST['group_allow'] ?? '';
|
||||
$contact_deny = $_REQUEST['contact_deny'] ?? '';
|
||||
$group_deny = $_REQUEST['group_deny'] ?? '';
|
||||
|
||||
if ($contact_allow
|
||||
. $group_allow
|
||||
. $contact_deny
|
||||
. $group_deny)
|
||||
{
|
||||
$contact_allow_list = $_REQUEST['contact_allow'] ? explode(',', $_REQUEST['contact_allow']) : [];
|
||||
$group_allow_list = $_REQUEST['group_allow'] ? explode(',', $_REQUEST['group_allow']) : [];
|
||||
$contact_deny_list = $_REQUEST['contact_deny'] ? explode(',', $_REQUEST['contact_deny']) : [];
|
||||
$group_deny_list = $_REQUEST['group_deny'] ? explode(',', $_REQUEST['group_deny']) : [];
|
||||
$contact_allow_list = $contact_allow ? explode(',', $contact_allow) : [];
|
||||
$group_allow_list = $group_allow ? explode(',', $group_allow) : [];
|
||||
$contact_deny_list = $contact_deny ? explode(',', $contact_deny) : [];
|
||||
$group_deny_list = $group_deny ? explode(',', $group_deny) : [];
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -126,7 +126,7 @@ class NodeInfo extends BaseModule
|
||||
$nodeinfo = [
|
||||
'version' => '1.0',
|
||||
'software' => [
|
||||
'name' => 'friendica',
|
||||
'name' => 'Friendica',
|
||||
'version' => FRIENDICA_VERSION . '-' . DB_UPDATE_VERSION,
|
||||
],
|
||||
'protocols' => [
|
||||
@@ -191,7 +191,7 @@ class NodeInfo extends BaseModule
|
||||
$nodeinfo = [
|
||||
'version' => '2.0',
|
||||
'software' => [
|
||||
'name' => 'friendica',
|
||||
'name' => 'Friendica',
|
||||
'version' => FRIENDICA_VERSION . '-' . DB_UPDATE_VERSION,
|
||||
],
|
||||
'protocols' => ['dfrn', 'activitypub'],
|
||||
|
||||
@@ -67,7 +67,7 @@ class Verify extends BaseModule
|
||||
'$errors_label' => L10n::tt('Error', 'Errors', count(self::$errors)),
|
||||
'$errors' => self::$errors,
|
||||
'$recovery_message' => L10n::t('Don’t have your phone? <a href="%s">Enter a two-factor recovery code</a>', '2fa/recovery'),
|
||||
'$verify_code' => ['verify_code', L10n::t('Please enter a code from your authentication app'), '', '', 'required', 'autofocus placeholder="000000"', 'number'],
|
||||
'$verify_code' => ['verify_code', L10n::t('Please enter a code from your authentication app'), '', '', 'required', 'autofocus placeholder="000000"', 'tel'],
|
||||
'$verify_label' => L10n::t('Verify code and complete login'),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1574,7 +1574,7 @@ class Probe
|
||||
$attr[$attribute->name] = trim($attribute->value);
|
||||
}
|
||||
|
||||
if ($feed_url == "") {
|
||||
if (empty($feed_url) && !empty($attr['href'])) {
|
||||
$feed_url = $attr["href"];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,7 +252,7 @@ class Delivery extends BaseObject
|
||||
private static function deliverDFRN($cmd, $contact, $owner, $items, $target_item, $public_message, $top_level, $followup)
|
||||
{
|
||||
// Transmit Diaspora reshares via Diaspora if the Friendica contact support Diaspora
|
||||
if (Diaspora::isReshare($target_item['body']) && !empty(Diaspora::personByHandle(contact['addr'], false))) {
|
||||
if (Diaspora::isReshare($target_item['body']) && !empty(Diaspora::personByHandle($contact['addr'], false))) {
|
||||
Logger::info('Reshare will be transmitted via Diaspora', ['url' => $contact['url'], 'guid' => ($target_item['guid'] ?? '') ?: $target_item['id']]);
|
||||
self::deliverDiaspora($cmd, $contact, $owner, $items, $target_item, $public_message, $top_level, $followup);
|
||||
return;
|
||||
|
||||
@@ -562,7 +562,7 @@ class OnePoll
|
||||
}
|
||||
|
||||
// Decoding the header
|
||||
$subject = imap_mime_header_decode($meta->subject);
|
||||
$subject = imap_mime_header_decode($meta->subject ?? '');
|
||||
$datarray['title'] = "";
|
||||
foreach ($subject as $subpart) {
|
||||
if ($subpart->charset != "default") {
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
use Friendica\Database\DBA;
|
||||
|
||||
if (!defined('DB_UPDATE_VERSION')) {
|
||||
define('DB_UPDATE_VERSION', 1324);
|
||||
define('DB_UPDATE_VERSION', 1327);
|
||||
}
|
||||
|
||||
return [
|
||||
@@ -102,6 +102,9 @@ return [
|
||||
"pubkey" => ["type" => "text", "comment" => ""],
|
||||
"baseurl" => ["type" => "varchar(255)", "comment" => "baseurl of the ap contact"],
|
||||
"generator" => ["type" => "varchar(255)", "comment" => "Name of the contact's system"],
|
||||
"following_count" => ["type" => "int unsigned", "default" => 0, "comment" => "Number of following contacts"],
|
||||
"followers_count" => ["type" => "int unsigned", "default" => 0, "comment" => "Number of followers"],
|
||||
"statuses_count" => ["type" => "int unsigned", "default" => 0, "comment" => "Number of posts"],
|
||||
"updated" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""]
|
||||
],
|
||||
"indexes" => [
|
||||
@@ -702,6 +705,7 @@ return [
|
||||
"resource-id" => ["resource-id"],
|
||||
"deleted_changed" => ["deleted", "changed"],
|
||||
"uid_wall_changed" => ["uid", "wall", "changed"],
|
||||
"mention_uid_id" => ["mention", "uid", "id"],
|
||||
"uid_eventid" => ["uid", "event-id"],
|
||||
"icid" => ["icid"],
|
||||
"iaid" => ["iaid"],
|
||||
|
||||
@@ -30,6 +30,9 @@ return [
|
||||
'/api' => [
|
||||
'/v1' => [
|
||||
'/follow_requests' => [Module\Api\Mastodon\FollowRequests::class, [R::GET ]],
|
||||
'/follow_requests/{id:\d+}/{action}' => [Module\Api\Mastodon\FollowRequests::class, [ R::POST]],
|
||||
'/instance' => [Module\Api\Mastodon\Instance::class, [R::GET]],
|
||||
'/instance/peers' => [Module\Api\Mastodon\Instance\Peers::class, [R::GET]],
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ use Friendica\Core\Session;
|
||||
use Friendica\Core\Session\ISession;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Test\Util\Database\StaticDatabase;
|
||||
use Monolog\Handler\TestHandler;
|
||||
@@ -2929,8 +2930,8 @@ class ApiTest extends DatabaseTest
|
||||
*/
|
||||
public function testApiFfIds()
|
||||
{
|
||||
$result = api_ff_ids('json');
|
||||
$this->assertNull($result);
|
||||
$result = api_ff_ids('json', Contact::FOLLOWER);
|
||||
$this->assertEquals(['id' => []], $result);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2952,7 +2953,7 @@ class ApiTest extends DatabaseTest
|
||||
public function testApiFfIdsWithoutAuthenticatedUser()
|
||||
{
|
||||
$_SESSION['authenticated'] = false;
|
||||
api_ff_ids('json');
|
||||
api_ff_ids('json', Contact::FOLLOWER);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2963,7 +2964,7 @@ class ApiTest extends DatabaseTest
|
||||
public function testApiFriendsIds()
|
||||
{
|
||||
$result = api_friends_ids('json');
|
||||
$this->assertNull($result);
|
||||
$this->assertEquals(['id' => []], $result);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2974,7 +2975,7 @@ class ApiTest extends DatabaseTest
|
||||
public function testApiFollowersIds()
|
||||
{
|
||||
$result = api_followers_ids('json');
|
||||
$this->assertNull($result);
|
||||
$this->assertEquals(['id' => []], $result);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3723,28 +3724,6 @@ class ApiTest extends DatabaseTest
|
||||
$this->markTestIncomplete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the api_get_nick() function.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testApiGetNick()
|
||||
{
|
||||
$result = api_get_nick($this->otherUser['nurl']);
|
||||
$this->assertEquals('othercontact', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the api_get_nick() function with a wrong URL.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testApiGetNickWithWrongUrl()
|
||||
{
|
||||
$result = api_get_nick('wrong_url');
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the api_in_reply_to() function.
|
||||
*
|
||||
|
||||
+7
-7
@@ -13,13 +13,13 @@
|
||||
<whitelist>
|
||||
<directory suffix=".php">..</directory>
|
||||
<exclude>
|
||||
<directory suffix=".php">config/</directory>
|
||||
<directory suffix=".php">doc/</directory>
|
||||
<directory suffix=".php">images/</directory>
|
||||
<directory suffix=".php">library/</directory>
|
||||
<directory suffix=".php">spec/</directory>
|
||||
<directory suffix=".php">tests/</directory>
|
||||
<directory suffix=".php">view/</directory>
|
||||
<directory suffix=".php">../config</directory>
|
||||
<directory suffix=".php">../doc</directory>
|
||||
<directory suffix=".php">../images</directory>
|
||||
<directory suffix=".php">../library</directory>
|
||||
<directory suffix=".php">../spec</directory>
|
||||
<directory suffix=".php">../tests</directory>
|
||||
<directory suffix=".php">../view</directory>
|
||||
</exclude>
|
||||
</whitelist>
|
||||
</filter>
|
||||
|
||||
+12
@@ -396,3 +396,15 @@ function update_1323()
|
||||
return Update::SUCCESS;
|
||||
}
|
||||
|
||||
function update_1327()
|
||||
{
|
||||
$contacts = DBA::select('contact', ['uid', 'id', 'blocked', 'readonly'], ["`uid` != ? AND (`blocked` OR `readonly`) AND NOT `pending`", 0]);
|
||||
while ($contact = DBA::fetch($contacts)) {
|
||||
Contact::setBlockedForUser($contact['id'], $contact['uid'], $contact['blocked']);
|
||||
Contact::setIgnoredForUser($contact['id'], $contact['uid'], $contact['readonly']);
|
||||
}
|
||||
DBA::close($contacts);
|
||||
|
||||
return Update::SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
+7722
-7563
File diff suppressed because it is too large
Load Diff
+1717
-1682
File diff suppressed because it is too large
Load Diff
+7694
-7536
File diff suppressed because it is too large
Load Diff
+1720
-1685
File diff suppressed because it is too large
Load Diff
+7743
-7585
File diff suppressed because it is too large
Load Diff
+1731
-1696
File diff suppressed because it is too large
Load Diff
+9239
-8348
File diff suppressed because it is too large
Load Diff
+1916
-1726
File diff suppressed because it is too large
Load Diff
+8318
-8138
File diff suppressed because it is too large
Load Diff
+1653
-1613
File diff suppressed because it is too large
Load Diff
+8347
-8167
File diff suppressed because it is too large
Load Diff
+1780
-1740
File diff suppressed because it is too large
Load Diff
+8630
-8451
File diff suppressed because it is too large
Load Diff
+1640
-1600
File diff suppressed because it is too large
Load Diff
@@ -145,6 +145,15 @@
|
||||
identify: function(obj) { return obj.type + '-' + obj.id.toString(); },
|
||||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace(['name', 'addr']),
|
||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||
sorter: function (itemA, itemB) {
|
||||
if (itemA.name === itemB.name) {
|
||||
return 0;
|
||||
} else if (itemA.name > itemB.name) {
|
||||
return 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
},
|
||||
});
|
||||
acl.initialize();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user