Merge branch 'master' 2019.12 into develop
This commit is contained in:
commit
00756737b5
57
CHANGELOG
57
CHANGELOG
|
@ -1,11 +1,59 @@
|
||||||
Version 2019.12-dev (unreleased)
|
Version 2019.12 (2019-12-23)
|
||||||
Friendica Core:
|
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 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 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 router configuration file [nupplaphil]
|
||||||
Added drone.io as CI service [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:
|
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:
|
mailstream:
|
||||||
Support for new img format was added [mexon]
|
Support for new img format was added [mexon]
|
||||||
BB Code is now included as plaintext [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]
|
ActivityPub "announce" notifications are not included [mexon]
|
||||||
|
|
||||||
Closed Issues:
|
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)
|
Version 2019.09 (2019-09-29)
|
||||||
Friendica Core:
|
Friendica Core:
|
||||||
|
|
12
CREDITS.txt
12
CREDITS.txt
|
@ -1,5 +1,3 @@
|
||||||
|
|
||||||
|
|
||||||
23n
|
23n
|
||||||
Abinoam P. Marques Jr.
|
Abinoam P. Marques Jr.
|
||||||
Abraham Pérez Hernández
|
Abraham Pérez Hernández
|
||||||
|
@ -47,6 +45,7 @@ bufalo1973
|
||||||
Calango Jr
|
Calango Jr
|
||||||
Carlos Solís
|
Carlos Solís
|
||||||
Carsten Pfeiffer
|
Carsten Pfeiffer
|
||||||
|
Casper
|
||||||
Cat Gray
|
Cat Gray
|
||||||
Chris Case
|
Chris Case
|
||||||
Christian González
|
Christian González
|
||||||
|
@ -66,6 +65,7 @@ David Martín Miranda
|
||||||
David Rabel
|
David Rabel
|
||||||
Dean Townsley
|
Dean Townsley
|
||||||
Denis Chenu
|
Denis Chenu
|
||||||
|
dependabot[bot]
|
||||||
Devlon Duthie
|
Devlon Duthie
|
||||||
Diego Souza
|
Diego Souza
|
||||||
Domovoy
|
Domovoy
|
||||||
|
@ -115,6 +115,7 @@ Jens Tautenhahn
|
||||||
jensp
|
jensp
|
||||||
Jeroen De Meerleer
|
Jeroen De Meerleer
|
||||||
jeroenpraat
|
jeroenpraat
|
||||||
|
Joan Bar
|
||||||
JOduMonT
|
JOduMonT
|
||||||
Johannes Schwab
|
Johannes Schwab
|
||||||
John Brazil
|
John Brazil
|
||||||
|
@ -123,11 +124,13 @@ Jonny Tischbein
|
||||||
Josef Moravek
|
Josef Moravek
|
||||||
juanman
|
juanman
|
||||||
julia.domagalska
|
julia.domagalska
|
||||||
|
Julio Cova
|
||||||
Karel
|
Karel
|
||||||
Karolina
|
Karolina
|
||||||
Keith Fernie
|
Keith Fernie
|
||||||
Klaus Weidenbach
|
Klaus Weidenbach
|
||||||
Koyu Berteon
|
Koyu Berteon
|
||||||
|
kPherox
|
||||||
Kris
|
Kris
|
||||||
Lea1995polish
|
Lea1995polish
|
||||||
Leberwurscht
|
Leberwurscht
|
||||||
|
@ -162,18 +165,20 @@ Michalina
|
||||||
Mike Macgirvin
|
Mike Macgirvin
|
||||||
miqrogroove
|
miqrogroove
|
||||||
mytbk
|
mytbk
|
||||||
|
nathilia-peirce
|
||||||
Nicola Spanti
|
Nicola Spanti
|
||||||
Olaf Conradi
|
Olaf Conradi
|
||||||
Oliver
|
Oliver
|
||||||
Olivier
|
Olivier
|
||||||
Olivier Mehani
|
Olivier Mehani
|
||||||
Olivier Migeot
|
Olivier Migeot
|
||||||
Ozero Dien
|
ozero dien
|
||||||
Paolo Wave
|
Paolo Wave
|
||||||
Pascal
|
Pascal
|
||||||
Pascal Deklerck
|
Pascal Deklerck
|
||||||
Pavel Morozov
|
Pavel Morozov
|
||||||
PerigGouanvic
|
PerigGouanvic
|
||||||
|
peter
|
||||||
Peter Liebetrau
|
Peter Liebetrau
|
||||||
peturisfeld
|
peturisfeld
|
||||||
Phigger Phigger
|
Phigger Phigger
|
||||||
|
@ -193,6 +198,7 @@ Ralph
|
||||||
Ratten
|
Ratten
|
||||||
rcmaniac
|
rcmaniac
|
||||||
rebeka-catalina
|
rebeka-catalina
|
||||||
|
René Wagner
|
||||||
repat
|
repat
|
||||||
Ricardo Pereira
|
Ricardo Pereira
|
||||||
Rik 4
|
Rik 4
|
||||||
|
|
2
boot.php
2
boot.php
|
@ -32,7 +32,7 @@ use Friendica\Util\DateTimeFormat;
|
||||||
|
|
||||||
define('FRIENDICA_PLATFORM', 'Friendica');
|
define('FRIENDICA_PLATFORM', 'Friendica');
|
||||||
define('FRIENDICA_CODENAME', 'Dalmatian Bellflower');
|
define('FRIENDICA_CODENAME', 'Dalmatian Bellflower');
|
||||||
define('FRIENDICA_VERSION', '2019.12-dev');
|
define('FRIENDICA_VERSION', '2019.12');
|
||||||
define('DFRN_PROTOCOL_VERSION', '2.23');
|
define('DFRN_PROTOCOL_VERSION', '2.23');
|
||||||
define('NEW_UPDATE_ROUTINE_VERSION', 1170);
|
define('NEW_UPDATE_ROUTINE_VERSION', 1170);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
-- ------------------------------------------
|
-- ------------------------------------------
|
||||||
-- Friendica 2019.12-dev (Dalmatian Bellflower)
|
-- Friendica 2019.12-rc (Dalmatian Bellflower)
|
||||||
-- DB_UPDATE_VERSION 1324
|
-- DB_UPDATE_VERSION 1326
|
||||||
-- ------------------------------------------
|
-- ------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@ -66,6 +66,9 @@ CREATE TABLE IF NOT EXISTS `apcontact` (
|
||||||
`pubkey` text COMMENT '',
|
`pubkey` text COMMENT '',
|
||||||
`baseurl` varchar(255) COMMENT 'baseurl of the ap contact',
|
`baseurl` varchar(255) COMMENT 'baseurl of the ap contact',
|
||||||
`generator` varchar(255) COMMENT 'Name of the contact\'s system',
|
`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 '',
|
`updated` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
|
||||||
PRIMARY KEY(`url`),
|
PRIMARY KEY(`url`),
|
||||||
INDEX `addr` (`addr`(32)),
|
INDEX `addr` (`addr`(32)),
|
||||||
|
@ -634,6 +637,7 @@ CREATE TABLE IF NOT EXISTS `item` (
|
||||||
INDEX `resource-id` (`resource-id`),
|
INDEX `resource-id` (`resource-id`),
|
||||||
INDEX `deleted_changed` (`deleted`,`changed`),
|
INDEX `deleted_changed` (`deleted`,`changed`),
|
||||||
INDEX `uid_wall_changed` (`uid`,`wall`,`changed`),
|
INDEX `uid_wall_changed` (`uid`,`wall`,`changed`),
|
||||||
|
INDEX `mention_uid_id` (`mention`,`uid`,`id`),
|
||||||
INDEX `uid_eventid` (`uid`,`event-id`),
|
INDEX `uid_eventid` (`uid`,`event-id`),
|
||||||
INDEX `icid` (`icid`),
|
INDEX `icid` (`icid`),
|
||||||
INDEX `iaid` (`iaid`),
|
INDEX `iaid` (`iaid`),
|
||||||
|
|
|
@ -11,14 +11,25 @@ Authentication is the same as described in [Using the APIs](help/api#Authenticat
|
||||||
|
|
||||||
## Entities
|
## 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
|
## 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
|
## 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)
|
- [`GET /api/v1/instance/activity`](https://docs.joinmastodon.org/methods/instance#weekly-activity)
|
||||||
- [POST /api/v1/follow_requests/:id/reject](https://docs.joinmastodon.org/api/rest/follow-requests/#post-api-v1-follow-requests-id-reject)
|
|
||||||
|
|
||||||
|
|
|
@ -172,7 +172,7 @@ Here is a list of known working clients:
|
||||||
|
|
||||||
* Android
|
* 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))
|
* [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/)
|
* [DiCa](https://dica.mixi.cool/)
|
||||||
* AndStatus
|
* AndStatus
|
||||||
* Twidere
|
* Twidere
|
||||||
|
|
|
@ -55,7 +55,7 @@ Friendica Documentation and Resources
|
||||||
* [Move classes to `src`](help/Developer-How-To-Move-Classes-to-src)
|
* [Move classes to `src`](help/Developer-How-To-Move-Classes-to-src)
|
||||||
* [Run tests](help/Tests)
|
* [Run tests](help/Tests)
|
||||||
* Reference
|
* Reference
|
||||||
* [Twitter/GNU Social API Functions](help/api)
|
* [API endpoints](help/api)
|
||||||
* [Code (Doxygen generated - sets cookies)](doc/html/)
|
* [Code (Doxygen generated - sets cookies)](doc/html/)
|
||||||
* [Protocol Documentation](help/Protocol)
|
* [Protocol Documentation](help/Protocol)
|
||||||
* [Database schema documentation](help/database)
|
* [Database schema documentation](help/database)
|
||||||
|
|
|
@ -30,7 +30,7 @@ The account will expire after 7 days, but you can ask the server admin to keep y
|
||||||
|
|
||||||
### Requirements
|
### 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 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
|
* 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
|
* 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.
|
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
|
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.
|
||||||
`.htaccess-dist` - as "dot" files are often hidden and aren't normally copied.
|
|
||||||
|
|
||||||
**OR**
|
**OR**
|
||||||
|
|
||||||
|
@ -112,7 +111,8 @@ Please check the [troubleshooting](#Troubleshooting) section if running on MySQL
|
||||||
|
|
||||||
### Option A: Run the installer
|
### 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.
|
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.
|
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
|
### 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.
|
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.
|
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.
|
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
|
### Option A: Der manuelle Installer
|
||||||
|
|
||||||
Besuche deine Webseite mit deinem Browser und befolge die Anleitung.
|
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.
|
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.
|
Falls du einen Port für die Datenbankverbindung angeben musst, kannst du diesen in der Host-Eingabe Zeile angeben.
|
||||||
|
|
277
include/api.php
277
include/api.php
|
@ -608,11 +608,6 @@ function api_get_user(App $a, $contact_id = null)
|
||||||
$contact = DBA::selectFirst('contact', [], ['uid' => 0, 'nurl' => Strings::normaliseLink($url)]);
|
$contact = DBA::selectFirst('contact', [], ['uid' => 0, 'nurl' => Strings::normaliseLink($url)]);
|
||||||
|
|
||||||
if (DBA::isResult($contact)) {
|
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 = [
|
$ret = [
|
||||||
'id' => $contact["id"],
|
'id' => $contact["id"],
|
||||||
'id_str' => (string) $contact["id"],
|
'id_str' => (string) $contact["id"],
|
||||||
|
@ -671,11 +666,6 @@ function api_get_user(App $a, $contact_id = null)
|
||||||
$countfollowers = 0;
|
$countfollowers = 0;
|
||||||
$starred = 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);
|
$pcontact_id = Contact::getIdForURL($uinfo[0]['url'], 0, true);
|
||||||
|
|
||||||
if (!empty($profile['about'])) {
|
if (!empty($profile['about'])) {
|
||||||
|
@ -1419,32 +1409,37 @@ function api_users_search($type)
|
||||||
$userlist = [];
|
$userlist = [];
|
||||||
|
|
||||||
if (!empty($_GET['q'])) {
|
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)) {
|
if (DBA::isResult($contacts)) {
|
||||||
$r = q("SELECT `id` FROM `contact` WHERE `uid` = 0 AND `nick` = '%s'", DBA::escape($_GET["q"]));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DBA::isResult($r)) {
|
|
||||||
$k = 0;
|
$k = 0;
|
||||||
foreach ($r as $user) {
|
foreach ($contacts as $contact) {
|
||||||
$user_info = api_get_user($a, $user["id"]);
|
$user_info = api_get_user($a, $contact['id']);
|
||||||
|
|
||||||
if ($type == "xml") {
|
if ($type == 'xml') {
|
||||||
$userlist[$k++.":user"] = $user_info;
|
$userlist[$k++ . ':user'] = $user_info;
|
||||||
} else {
|
} else {
|
||||||
$userlist[] = $user_info;
|
$userlist[] = $user_info;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$userlist = ["users" => $userlist];
|
$userlist = ['users' => $userlist];
|
||||||
} else {
|
} else {
|
||||||
throw new BadRequestException("User ".$_GET["q"]." not found.");
|
throw new NotFoundException('User ' . $_GET['q'] . ' not found.');
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
/// @TODO move to top of file or somewhere better
|
||||||
|
@ -1505,7 +1500,9 @@ function api_search($type)
|
||||||
$a = \get_app();
|
$a = \get_app();
|
||||||
$user_info = api_get_user($a);
|
$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'])) {
|
if (empty($_REQUEST['q'])) {
|
||||||
throw new BadRequestException('q parameter is required.');
|
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);
|
$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);
|
$start = max(0, ($page - 1) * $count);
|
||||||
|
|
||||||
$condition = ["`uid` = ? AND `gravity` IN (?, ?) AND `item`.`id` > ? AND `author-id` != ?
|
$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 `thread`.`mention` AND NOT `thread`.`ignored`)",
|
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()];
|
api_user(), GRAVITY_PARENT, GRAVITY_COMMENT, $since_id, $user_info['pid'], api_user()];
|
||||||
|
|
||||||
if ($max_id > 0) {
|
if ($max_id > 0) {
|
||||||
|
@ -2486,6 +2497,9 @@ function api_format_messages($item, $recipient, $sender)
|
||||||
function api_convert_item($item)
|
function api_convert_item($item)
|
||||||
{
|
{
|
||||||
$body = $item['body'];
|
$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);
|
$attachments = api_get_attachments($body);
|
||||||
|
|
||||||
// Workaround for ostatus messages where the title is identically to the 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']);
|
$statushtml .= BBCode::convert($item['plink']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$entities = api_get_entitities($statustext, $body);
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"text" => $statustext,
|
"text" => $statustext,
|
||||||
"html" => $statushtml,
|
"html" => $statushtml,
|
||||||
|
@ -2561,17 +2573,19 @@ function api_convert_item($item)
|
||||||
*/
|
*/
|
||||||
function api_get_attachments(&$body)
|
function api_get_attachments(&$body)
|
||||||
{
|
{
|
||||||
$text = $body;
|
$body = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $body);
|
||||||
$text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $text);
|
$body = preg_replace("/\[img\=(.*?)\](.*?)\[\/img\]/ism", '[img]$1[/img]', $body);
|
||||||
$text = preg_replace("/\[img\=(.*?)\](.*?)\[\/img\]/ism", '[img]$1[/img]', $text);
|
|
||||||
|
|
||||||
$URLSearchString = "^\[\]";
|
$URLSearchString = "^\[\]";
|
||||||
$ret = preg_match_all("/\[img\]([$URLSearchString]*)\[\/img\]/ism", $text, $images);
|
if (!preg_match_all("/\[img\]([$URLSearchString]*)\[\/img\]/ism", $body, $images)) {
|
||||||
|
|
||||||
if (!$ret) {
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove all embedded pictures, since they are added as attachments
|
||||||
|
foreach ($images[0] as $orig) {
|
||||||
|
$body = str_replace($orig, '', $body);
|
||||||
|
}
|
||||||
|
|
||||||
$attachments = [];
|
$attachments = [];
|
||||||
|
|
||||||
foreach ($images[1] as $image) {
|
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;
|
return $attachments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2630,7 +2638,6 @@ function api_get_entitities(&$text, $bbcode)
|
||||||
$bbcode = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '#$2', $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("/\[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("/\[video\](.*?)\[\/video\]/ism", '[url=$1]$1[/url]', $bbcode);
|
||||||
|
|
||||||
$bbcode = preg_replace(
|
$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);
|
$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);
|
preg_match_all("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", $bbcode, $urls);
|
||||||
|
|
||||||
$ordered_urls = [];
|
$ordered_urls = [];
|
||||||
foreach ($urls[1] as $id => $url) {
|
foreach ($urls[1] as $id => $url) {
|
||||||
//$start = strpos($text, $url, $offset);
|
|
||||||
$start = iconv_strpos($text, $url, 0, "UTF-8");
|
$start = iconv_strpos($text, $url, 0, "UTF-8");
|
||||||
if (!($start === false)) {
|
if (!($start === false)) {
|
||||||
$ordered_urls[$start] = ["url" => $url, "title" => $urls[2][$id]];
|
$ordered_urls[$start] = ["url" => $url, "title" => $urls[2][$id]];
|
||||||
|
@ -2664,7 +2669,7 @@ function api_get_entitities(&$text, $bbcode)
|
||||||
ksort($ordered_urls);
|
ksort($ordered_urls);
|
||||||
|
|
||||||
$offset = 0;
|
$offset = 0;
|
||||||
//foreach ($urls[1] AS $id=>$url) {
|
|
||||||
foreach ($ordered_urls as $url) {
|
foreach ($ordered_urls as $url) {
|
||||||
if ((substr($url["title"], 0, 7) != "http://") && (substr($url["title"], 0, 8) != "https://")
|
if ((substr($url["title"], 0, 7) != "http://") && (substr($url["title"], 0, 8) != "https://")
|
||||||
&& !strpos($url["title"], "http://") && !strpos($url["title"], "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");
|
$start = iconv_strpos($text, $url["url"], $offset, "UTF-8");
|
||||||
if (!($start === false)) {
|
if (!($start === false)) {
|
||||||
$entities["urls"][] = ["url" => $url["url"],
|
$entities["urls"][] = ["url" => $url["url"],
|
||||||
|
@ -2706,7 +2710,7 @@ function api_get_entitities(&$text, $bbcode)
|
||||||
$ordered_images[$start] = ['url' => $image, 'alt' => ''];
|
$ordered_images[$start] = ['url' => $image, 'alt' => ''];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//$entities["media"] = array();
|
|
||||||
$offset = 0;
|
$offset = 0;
|
||||||
|
|
||||||
foreach ($ordered_images as $image) {
|
foreach ($ordered_images as $image) {
|
||||||
|
@ -2840,9 +2844,10 @@ function api_format_items_activities($item, $type = "json")
|
||||||
'attendyes' => [],
|
'attendyes' => [],
|
||||||
'attendno' => [],
|
'attendno' => [],
|
||||||
'attendmaybe' => [],
|
'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);
|
$ret = Item::selectForUser($item['uid'], ['author-id', 'verb'], $condition);
|
||||||
|
|
||||||
while ($parent_item = Item::fetch($ret)) {
|
while ($parent_item = Item::fetch($ret)) {
|
||||||
|
@ -2867,6 +2872,9 @@ function api_format_items_activities($item, $type = "json")
|
||||||
case Activity::ATTENDMAYBE:
|
case Activity::ATTENDMAYBE:
|
||||||
$activities['attendmaybe'][] = $user;
|
$activities['attendmaybe'][] = $user;
|
||||||
break;
|
break;
|
||||||
|
case Activity::ANNOUNCE:
|
||||||
|
$activities['announce'][] = $user;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -3075,12 +3083,16 @@ function api_format_item($item, $type = "json", $status_user = null, $author_use
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($quoted_item)) {
|
if (!empty($quoted_item)) {
|
||||||
|
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);
|
$conv_quoted = api_convert_item($quoted_item);
|
||||||
$quoted_status = $status;
|
$quoted_status = $status;
|
||||||
unset($quoted_status['friendica_author']);
|
unset($quoted_status['attachments']);
|
||||||
unset($quoted_status['friendica_owner']);
|
unset($quoted_status['entities']);
|
||||||
unset($quoted_status['friendica_activities']);
|
|
||||||
unset($quoted_status['friendica_private']);
|
|
||||||
unset($quoted_status['statusnet_conversation_id']);
|
unset($quoted_status['statusnet_conversation_id']);
|
||||||
$quoted_status['text'] = $conv_quoted['text'];
|
$quoted_status['text'] = $conv_quoted['text'];
|
||||||
$quoted_status['statusnet_html'] = $conv_quoted['html'];
|
$quoted_status['statusnet_html'] = $conv_quoted['html'];
|
||||||
|
@ -3092,6 +3104,11 @@ function api_format_item($item, $type = "json", $status_user = null, $author_use
|
||||||
$quoted_status["user"] = [];
|
$quoted_status["user"] = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
unset($quoted_status['friendica_author']);
|
||||||
|
unset($quoted_status['friendica_owner']);
|
||||||
|
unset($quoted_status['friendica_activities']);
|
||||||
|
unset($quoted_status['friendica_private']);
|
||||||
|
}
|
||||||
|
|
||||||
if (!empty($retweeted_item)) {
|
if (!empty($retweeted_item)) {
|
||||||
$retweeted_status = $status;
|
$retweeted_status = $status;
|
||||||
|
@ -3606,6 +3623,7 @@ api_register_func('api/statusnet/version', 'api_statusnet_version', false);
|
||||||
*
|
*
|
||||||
* @param string $type Return type (atom, rss, xml, json)
|
* @param string $type Return type (atom, rss, xml, json)
|
||||||
*
|
*
|
||||||
|
* @param int $rel A contact relationship constant
|
||||||
* @return array|string|void
|
* @return array|string|void
|
||||||
* @throws BadRequestException
|
* @throws BadRequestException
|
||||||
* @throws ForbiddenException
|
* @throws ForbiddenException
|
||||||
|
@ -3614,7 +3632,7 @@ api_register_func('api/statusnet/version', 'api_statusnet_version', false);
|
||||||
* @throws UnauthorizedException
|
* @throws UnauthorizedException
|
||||||
* @todo use api_format_data() to return data
|
* @todo use api_format_data() to return data
|
||||||
*/
|
*/
|
||||||
function api_ff_ids($type)
|
function api_ff_ids($type, int $rel)
|
||||||
{
|
{
|
||||||
if (!api_user()) {
|
if (!api_user()) {
|
||||||
throw new ForbiddenException();
|
throw new ForbiddenException();
|
||||||
|
@ -3626,26 +3644,29 @@ function api_ff_ids($type)
|
||||||
|
|
||||||
$stringify_ids = $_REQUEST['stringify_ids'] ?? false;
|
$stringify_ids = $_REQUEST['stringify_ids'] ?? false;
|
||||||
|
|
||||||
$r = q(
|
$contacts = DBA::p("SELECT `pcontact`.`id`
|
||||||
"SELECT `pcontact`.`id` FROM `contact`
|
FROM `contact`
|
||||||
INNER JOIN `contact` AS `pcontact` ON `contact`.`nurl` = `pcontact`.`nurl` AND `pcontact`.`uid` = 0
|
INNER JOIN `contact` AS `pcontact`
|
||||||
WHERE `contact`.`uid` = %s AND NOT `contact`.`self`",
|
ON `contact`.`nurl` = `pcontact`.`nurl`
|
||||||
intval(api_user())
|
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 = [];
|
$ids = [];
|
||||||
foreach ($r as $rr) {
|
foreach (DBA::toArray($contacts) as $contact) {
|
||||||
if ($stringify_ids) {
|
if ($stringify_ids) {
|
||||||
$ids[] = $rr['id'];
|
$ids[] = $contact['id'];
|
||||||
} else {
|
} 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
|
* @return array|string
|
||||||
* @throws BadRequestException
|
* @throws BadRequestException
|
||||||
* @throws ForbiddenException
|
* @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
|
* @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-ids
|
||||||
*/
|
*/
|
||||||
function api_friends_ids($type)
|
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
|
* @return array|string
|
||||||
* @throws BadRequestException
|
* @throws BadRequestException
|
||||||
* @throws ForbiddenException
|
* @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
|
* @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-ids
|
||||||
*/
|
*/
|
||||||
function api_followers_ids($type)
|
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
|
/// @TODO move to top of file or somewhere better
|
||||||
|
@ -5064,14 +5091,17 @@ function api_friendica_remoteauth()
|
||||||
// traditional DFRN
|
// traditional DFRN
|
||||||
|
|
||||||
$contact = DBA::selectFirst('contact', [], ['uid' => api_user(), 'nurl' => $c_url]);
|
$contact = DBA::selectFirst('contact', [], ['uid' => api_user(), 'nurl' => $c_url]);
|
||||||
|
if (!DBA::isResult($contact)) {
|
||||||
if (!DBA::isResult($contact) || ($contact['network'] !== Protocol::DFRN)) {
|
|
||||||
throw new BadRequestException("Unknown contact");
|
throw new BadRequestException("Unknown contact");
|
||||||
}
|
}
|
||||||
|
|
||||||
$cid = $contact['id'];
|
$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']) {
|
if ($contact['duplex'] && $contact['issued-id']) {
|
||||||
$orig_id = $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["created"] = $reshared['posted'];
|
||||||
$reshared_item["edited"] = $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;
|
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
|
* @param array $item
|
||||||
|
@ -5303,10 +5264,6 @@ function api_in_reply_to($item)
|
||||||
$parent = Item::selectFirst($fields, ['id' => $in_reply_to['status_id']]);
|
$parent = Item::selectFirst($fields, ['id' => $in_reply_to['status_id']]);
|
||||||
|
|
||||||
if (DBA::isResult($parent)) {
|
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['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'] = intval($parent['author-id']);
|
||||||
$in_reply_to['user_id_str'] = (string) intval($parent['author-id']);
|
$in_reply_to['user_id_str'] = (string) intval($parent['author-id']);
|
||||||
|
|
|
@ -11,6 +11,7 @@ use Friendica\Core\Logger;
|
||||||
use Friendica\Core\Renderer;
|
use Friendica\Core\Renderer;
|
||||||
use Friendica\Core\System;
|
use Friendica\Core\System;
|
||||||
use Friendica\Database\DBA;
|
use Friendica\Database\DBA;
|
||||||
|
use Friendica\Model\Contact;
|
||||||
use Friendica\Model\Item;
|
use Friendica\Model\Item;
|
||||||
use Friendica\Model\User;
|
use Friendica\Model\User;
|
||||||
use Friendica\Protocol\Activity;
|
use Friendica\Protocol\Activity;
|
||||||
|
@ -775,17 +776,18 @@ function check_item_notification($itemid, $uid, $defaulttype = "") {
|
||||||
|
|
||||||
if ($item["parent-uri"] === $item["uri"]) {
|
if ($item["parent-uri"] === $item["uri"]) {
|
||||||
// Send a notification for every new post?
|
// 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]);
|
$send_notification = DBA::exists('contact', ['id' => $item['contact-id'], 'notify_new_posts' => true]);
|
||||||
|
|
||||||
|
// Or the contact is a mentioned forum
|
||||||
if (!$send_notification) {
|
if (!$send_notification) {
|
||||||
$tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
|
$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));
|
intval(TERM_OBJ_POST), intval($itemid), intval(TERM_MENTION), intval($uid));
|
||||||
|
|
||||||
if (DBA::isResult($tags)) {
|
if (DBA::isResult($tags)) {
|
||||||
foreach ($tags AS $tag) {
|
foreach ($tags AS $tag) {
|
||||||
$condition = ['nurl' => Strings::normaliseLink($tag["url"]), 'uid' => $uid, 'notify_new_posts' => true];
|
$condition = ['nurl' => Strings::normaliseLink($tag["url"]), 'uid' => $uid, 'notify_new_posts' => true, 'contact-type' => Contact::TYPE_COMMUNITY];
|
||||||
$r = DBA::exists('contact', $condition);
|
if (DBA::exists('contact', $condition)) {
|
||||||
if ($r) {
|
|
||||||
$send_notification = true;
|
$send_notification = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,7 +136,7 @@ function common_content(App $a)
|
||||||
$title = '';
|
$title = '';
|
||||||
$tab_str = '';
|
$tab_str = '';
|
||||||
if ($cmd === 'loc' && $cid && local_user() == $uid) {
|
if ($cmd === 'loc' && $cid && local_user() == $uid) {
|
||||||
$tab_str = Module\Contact::getTabsHTML($a, $contact, 4);
|
$tab_str = Module\Contact::getTabsHTML($a, $contact, 5);
|
||||||
} else {
|
} else {
|
||||||
$title = L10n::t('Common Friends');
|
$title = L10n::t('Common Friends');
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,7 +134,7 @@ function crepair_content(App $a)
|
||||||
|
|
||||||
$update_profile = in_array($contact['network'], Protocol::FEDERATED);
|
$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');
|
$tpl = Renderer::getMarkupTemplate('crepair.tpl');
|
||||||
$o = Renderer::replaceMacros($tpl, [
|
$o = Renderer::replaceMacros($tpl, [
|
||||||
|
|
|
@ -93,7 +93,8 @@ function display_init(App $a)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($item["id"] != $item["parent"]) {
|
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);
|
$profiledata = display_fetchauthor($a, $item);
|
||||||
|
@ -171,6 +172,8 @@ function display_content(App $a, $update = false, $update_uid = 0)
|
||||||
|
|
||||||
$o = '';
|
$o = '';
|
||||||
|
|
||||||
|
$item = null;
|
||||||
|
|
||||||
if ($update) {
|
if ($update) {
|
||||||
$item_id = $_REQUEST['item_id'];
|
$item_id = $_REQUEST['item_id'];
|
||||||
$item = Item::selectFirst(['uid', 'parent', 'parent-uri'], ['id' => $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.'));
|
throw new HTTPException\NotFoundException(L10n::t('The requested item doesn\'t exist or has been deleted.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,8 +245,11 @@ function display_content(App $a, $update = false, $update_uid = 0)
|
||||||
$is_remote_contact = false;
|
$is_remote_contact = false;
|
||||||
$item_uid = local_user();
|
$item_uid = local_user();
|
||||||
|
|
||||||
if (isset($item_parent_uri)) {
|
$parent = null;
|
||||||
|
if (!empty($item_parent_uri)) {
|
||||||
$parent = Item::selectFirst(['uid'], ['uri' => $item_parent_uri, 'wall' => true]);
|
$parent = Item::selectFirst(['uid'], ['uri' => $item_parent_uri, 'wall' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
if (DBA::isResult($parent)) {
|
if (DBA::isResult($parent)) {
|
||||||
$a->profile['uid'] = ($a->profile['uid'] ?? 0) ?: $parent['uid'];
|
$a->profile['uid'] = ($a->profile['uid'] ?? 0) ?: $parent['uid'];
|
||||||
$a->profile['profile_uid'] = ($a->profile['profile_uid'] ?? 0) ?: $parent['uid'];
|
$a->profile['profile_uid'] = ($a->profile['profile_uid'] ?? 0) ?: $parent['uid'];
|
||||||
|
@ -251,7 +257,8 @@ function display_content(App $a, $update = false, $update_uid = 0)
|
||||||
if ($is_remote_contact) {
|
if ($is_remote_contact) {
|
||||||
$item_uid = $parent['uid'];
|
$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']]);
|
$page_contact = DBA::selectFirst('contact', [], ['self' => true, 'uid' => $a->profile['uid']]);
|
||||||
|
|
|
@ -14,7 +14,7 @@ use Friendica\Core\Renderer;
|
||||||
use Friendica\Core\System;
|
use Friendica\Core\System;
|
||||||
use Friendica\Database\DBA;
|
use Friendica\Database\DBA;
|
||||||
use Friendica\Module\Login;
|
use Friendica\Module\Login;
|
||||||
use Friendica\Model\Contact;
|
use Friendica\Model\Introduction;
|
||||||
use Friendica\Model\Notify;
|
use Friendica\Model\Notify;
|
||||||
|
|
||||||
function notifications_post(App $a)
|
function notifications_post(App $a)
|
||||||
|
@ -30,44 +30,21 @@ function notifications_post(App $a)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request_id) {
|
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)) {
|
switch ($_POST['submit']) {
|
||||||
$intro_id = $intro['id'];
|
case L10n::t('Discard'):
|
||||||
$contact_id = $intro['contact-id'];
|
$Intro->discard();
|
||||||
} else {
|
break;
|
||||||
notice(L10n::t('Invalid request identifier.') . EOL);
|
case L10n::t('Ignore'):
|
||||||
return;
|
$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');
|
$a->internalRedirect('notifications/intros');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($_POST['submit'] == L10n::t('Ignore')) {
|
|
||||||
DBA::update('intro', ['ignore' => true], ['id' => $intro_id]);
|
|
||||||
$a->internalRedirect('notifications/intros');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function notifications_content(App $a)
|
function notifications_content(App $a)
|
||||||
|
|
|
@ -4,6 +4,7 @@ namespace Friendica\Api\Mastodon;
|
||||||
|
|
||||||
use Friendica\Content\Text\BBCode;
|
use Friendica\Content\Text\BBCode;
|
||||||
use Friendica\Database\DBA;
|
use Friendica\Database\DBA;
|
||||||
|
use Friendica\Model\Contact;
|
||||||
use Friendica\Util\DateTimeFormat;
|
use Friendica\Util\DateTimeFormat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -55,23 +56,24 @@ class Account
|
||||||
/**
|
/**
|
||||||
* Creates an account record from a contact record. Expects all contact table fields to be set
|
* 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
|
* @return Account
|
||||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||||
*/
|
*/
|
||||||
public static function createFromContact(array $contact) {
|
public static function createFromContact(array $contact, array $apcontact = [])
|
||||||
|
{
|
||||||
$account = new Account();
|
$account = new Account();
|
||||||
$account->id = $contact['id'];
|
$account->id = $contact['id'];
|
||||||
$account->username = $contact['nick'];
|
$account->username = $contact['nick'];
|
||||||
$account->acct = $contact['nick'];
|
$account->acct = $contact['nick'];
|
||||||
$account->display_name = $contact['name'];
|
$account->display_name = $contact['name'];
|
||||||
$account->locked = $contact['blocked'];
|
$account->locked = !empty($apcontact['manually-approve']);
|
||||||
$account->created_at = DateTimeFormat::utc($contact['created'], DateTimeFormat::ATOM);
|
$account->created_at = DateTimeFormat::utc($contact['created'], DateTimeFormat::ATOM);
|
||||||
// No data is available from contact
|
$account->followers_count = $apcontact['followers_count'] ?? 0;
|
||||||
$account->followers_count = 0;
|
$account->following_count = $apcontact['following_count'] ?? 0;
|
||||||
$account->following_count = 0;
|
$account->statuses_count = $apcontact['statuses_count'] ?? 0;
|
||||||
$account->statuses_count = 0;
|
$account->note = BBCode::convert($contact['about'], false);
|
||||||
$account->note = BBCode::convert($contact['about']);
|
|
||||||
$account->url = $contact['url'];
|
$account->url = $contact['url'];
|
||||||
$account->avatar = $contact['avatar'];
|
$account->avatar = $contact['avatar'];
|
||||||
$account->avatar_static = $contact['avatar'];
|
$account->avatar_static = $contact['avatar'];
|
||||||
|
@ -80,6 +82,7 @@ class Account
|
||||||
$account->header_static = '';
|
$account->header_static = '';
|
||||||
// No custom emojis per account in Friendica
|
// No custom emojis per account in Friendica
|
||||||
$account->emojis = [];
|
$account->emojis = [];
|
||||||
|
$account->bot = ($contact['contact-type'] == Contact::TYPE_NEWS);
|
||||||
|
|
||||||
return $account;
|
return $account;
|
||||||
}
|
}
|
||||||
|
|
86
src/Api/Mastodon/Instance.php
Normal file
86
src/Api/Mastodon/Instance.php
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
59
src/Api/Mastodon/Relationship.php
Normal file
59
src/Api/Mastodon/Relationship.php
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
38
src/Api/Mastodon/Stats.php
Normal file
38
src/Api/Mastodon/Stats.php
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -385,7 +385,7 @@ class App
|
||||||
* @deprecated 2019.09 - Use BaseURL->remove() instead
|
* @deprecated 2019.09 - Use BaseURL->remove() instead
|
||||||
* @see BaseURL::remove()
|
* @see BaseURL::remove()
|
||||||
*/
|
*/
|
||||||
public function removeBaseURL($origURL)
|
public function removeBaseURL(string $origURL)
|
||||||
{
|
{
|
||||||
return $this->baseURL->remove($origURL);
|
return $this->baseURL->remove($origURL);
|
||||||
}
|
}
|
||||||
|
|
|
@ -251,10 +251,6 @@ class Module
|
||||||
|
|
||||||
call_user_func([$this->module_class, 'init'], $this->module_parameters);
|
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') {
|
if ($server['REQUEST_METHOD'] === 'POST') {
|
||||||
Core\Hook::callAll($this->module . '_mod_post', $post);
|
Core\Hook::callAll($this->module . '_mod_post', $post);
|
||||||
call_user_func([$this->module_class, 'post'], $this->module_parameters);
|
call_user_func([$this->module_class, 'post'], $this->module_parameters);
|
||||||
|
@ -262,5 +258,9 @@ class Module
|
||||||
|
|
||||||
Core\Hook::callAll($this->module . '_mod_afterpost', $placeholder);
|
Core\Hook::callAll($this->module . '_mod_afterpost', $placeholder);
|
||||||
call_user_func([$this->module_class, 'afterpost'], $this->module_parameters);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
95
src/BaseModel.php
Normal file
95
src/BaseModel.php
Normal file
|
@ -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];
|
$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
|
// Now count the number of external media links
|
||||||
preg_match_all("(\[vimeo\](.*?)\[\/vimeo\])ism", $post['text'], $links1, PREG_SET_ORDER);
|
preg_match_all("(\[vimeo\](.*?)\[\/vimeo\])ism", $post['text'], $links1, PREG_SET_ORDER);
|
||||||
preg_match_all("(\[youtube\\](.*?)\[\/youtube\\])ism", $post['text'], $links2, 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)
|
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) {
|
function ($match) use ($no_link_desc) {
|
||||||
$attach_data = self::getAttachmentData($match[0]);
|
$attach_data = self::getAttachmentData($match[0]);
|
||||||
if (empty($attach_data['url'])) {
|
if (empty($attach_data['url'])) {
|
||||||
return $match[0];
|
return $match[0];
|
||||||
} elseif (empty($attach_data['title']) || $no_link_desc) {
|
} elseif (empty($attach_data['title']) || $no_link_desc) {
|
||||||
return '[url]' . $attach_data['url'] . "[/url]\n";
|
return "\n[url]" . $attach_data['url'] . "[/url]\n";
|
||||||
} else {
|
} else {
|
||||||
return '[url=' . $attach_data['url'] . ']' . $attach_data['title'] . "[/url]\n";
|
return "\n[url=" . $attach_data['url'] . ']' . $attach_data['title'] . "[/url]\n";
|
||||||
}
|
}
|
||||||
}, $body);
|
}, $body);
|
||||||
}
|
}
|
||||||
|
@ -1504,8 +1507,29 @@ class BBCode extends BaseObject
|
||||||
$text = str_replace('[hr]', '<hr />', $text);
|
$text = str_replace('[hr]', '<hr />', $text);
|
||||||
|
|
||||||
if (!$for_plaintext) {
|
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
|
// Autolinker for isolated URLs
|
||||||
$text = preg_replace(Strings::autoLinkRegEx(), '[url]$1[/url]', $text);
|
$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()
|
// 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);
|
||||||
//$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
|
// Try to Oembed
|
||||||
if ($try_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);
|
$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 = [])
|
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('asset/typeahead.js/dist/typeahead.bundle.js'));
|
||||||
$page->registerFooterScript(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.js'));
|
$page->registerFooterScript(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.js'));
|
||||||
$page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.css'));
|
$page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.css'));
|
||||||
|
|
|
@ -41,7 +41,7 @@ class System extends BaseObject
|
||||||
* @return string The cleaned url
|
* @return string The cleaned url
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public static function removedBaseUrl($orig_url)
|
public static function removedBaseUrl(string $orig_url)
|
||||||
{
|
{
|
||||||
return self::getApp()->removeBaseURL($orig_url);
|
return self::getApp()->removeBaseURL($orig_url);
|
||||||
}
|
}
|
||||||
|
|
|
@ -334,7 +334,7 @@ class DBA extends BaseObject
|
||||||
/**
|
/**
|
||||||
* @brief Delete a row from a table
|
* @brief Delete a row from a table
|
||||||
*
|
*
|
||||||
* @param string $table Table name
|
* @param string|array $table Table name
|
||||||
* @param array $conditions Field condition(s)
|
* @param array $conditions Field condition(s)
|
||||||
* @param array $options
|
* @param array $options
|
||||||
* - cascade: If true we delete records in other tables that depend on the one we're deleting through
|
* - cascade: If true we delete records in other tables that depend on the one we're deleting through
|
||||||
|
@ -411,7 +411,7 @@ class DBA extends BaseObject
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
* @see self::select
|
* @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);
|
return self::getClass(Database::class)->selectToArray($table, $fields, $condition, $params);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1377,7 +1377,7 @@ class Database
|
||||||
*
|
*
|
||||||
* @brief Retrieve a single record from a table
|
* @brief Retrieve a single record from a table
|
||||||
*
|
*
|
||||||
* @param string $table
|
* @param string|array $table
|
||||||
* @param array $fields
|
* @param array $fields
|
||||||
* @param array $condition
|
* @param array $condition
|
||||||
* @param array $params
|
* @param array $params
|
||||||
|
@ -1412,7 +1412,7 @@ class Database
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
* @see self::select
|
* @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));
|
return $this->toArray($this->select($table, $fields, $condition, $params));
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@ class APContact extends BaseObject
|
||||||
public static function getByURL($url, $update = null)
|
public static function getByURL($url, $update = null)
|
||||||
{
|
{
|
||||||
if (empty($url)) {
|
if (empty($url)) {
|
||||||
return false;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$fetched_contact = false;
|
$fetched_contact = false;
|
||||||
|
@ -110,7 +110,7 @@ class APContact extends BaseObject
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_null($update)) {
|
if (!is_null($update)) {
|
||||||
return DBA::isResult($apcontact) ? $apcontact : false;
|
return DBA::isResult($apcontact) ? $apcontact : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DBA::isResult($apcontact)) {
|
if (DBA::isResult($apcontact)) {
|
||||||
|
@ -203,6 +203,33 @@ class APContact extends BaseObject
|
||||||
$apcontact['generator'] = JsonLD::fetchElement($compacted['as:generator'], 'as:name', '@value');
|
$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
|
// To-Do
|
||||||
|
|
||||||
// Unhandled
|
// Unhandled
|
||||||
|
|
|
@ -1230,7 +1230,7 @@ class Contact extends BaseObject
|
||||||
|
|
||||||
$follow_link = '';
|
$follow_link = '';
|
||||||
$unfollow_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])) {
|
if ($contact['uid'] && in_array($contact['rel'], [self::SHARING, self::FRIEND])) {
|
||||||
$unfollow_link = 'unfollow?url=' . urlencode($contact['url']);
|
$unfollow_link = 'unfollow?url=' . urlencode($contact['url']);
|
||||||
} elseif(!$contact['pending']) {
|
} elseif(!$contact['pending']) {
|
||||||
|
|
|
@ -876,7 +876,11 @@ class GContact
|
||||||
self::updateFromOutbox($outbox['first']['href'], $data);
|
self::updateFromOutbox($outbox['first']['href'], $data);
|
||||||
return;
|
return;
|
||||||
} elseif (!empty($outbox['first'])) {
|
} elseif (!empty($outbox['first'])) {
|
||||||
|
if (is_string($outbox['first'])) {
|
||||||
self::updateFromOutbox($outbox['first'], $data);
|
self::updateFromOutbox($outbox['first'], $data);
|
||||||
|
} else {
|
||||||
|
Logger::warning('Unexpected data', ['outbox' => $outbox]);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
$items = [];
|
$items = [];
|
||||||
|
|
|
@ -549,7 +549,7 @@ class GServer
|
||||||
$protocols[$protocol] = true;
|
$protocols[$protocol] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($protocols['friendica'])) {
|
if (!empty($protocols['dfrn'])) {
|
||||||
$server['network'] = Protocol::DFRN;
|
$server['network'] = Protocol::DFRN;
|
||||||
} elseif (!empty($protocols['activitypub'])) {
|
} elseif (!empty($protocols['activitypub'])) {
|
||||||
$server['network'] = Protocol::ACTIVITYPUB;
|
$server['network'] = Protocol::ACTIVITYPUB;
|
||||||
|
|
156
src/Model/Introduction.php
Normal file
156
src/Model/Introduction.php
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -2621,7 +2621,7 @@ class Item extends BaseObject
|
||||||
"#$2", $item["body"]);
|
"#$2", $item["body"]);
|
||||||
|
|
||||||
foreach ($tags as $tag) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,13 @@
|
||||||
|
|
||||||
namespace Friendica\Module\Api\Mastodon;
|
namespace Friendica\Module\Api\Mastodon;
|
||||||
|
|
||||||
use Friendica\Api\Mastodon\Account;
|
use Friendica\Api\Mastodon;
|
||||||
use Friendica\App\BaseURL;
|
use Friendica\App\BaseURL;
|
||||||
use Friendica\Core\System;
|
use Friendica\Core\System;
|
||||||
use Friendica\Database\DBA;
|
use Friendica\Database\DBA;
|
||||||
|
use Friendica\Model\APContact;
|
||||||
use Friendica\Model\Contact;
|
use Friendica\Model\Contact;
|
||||||
|
use Friendica\Model\Introduction;
|
||||||
use Friendica\Module\Base\Api;
|
use Friendica\Module\Base\Api;
|
||||||
use Friendica\Network\HTTPException;
|
use Friendica\Network\HTTPException;
|
||||||
|
|
||||||
|
@ -19,7 +21,40 @@ class FollowRequests extends Api
|
||||||
{
|
{
|
||||||
parent::init($parameters);
|
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);
|
$limit = intval($_GET['limit'] ?? 40);
|
||||||
|
|
||||||
if (isset($since_id) && isset($max_id)) {
|
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)) {
|
} 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)) {
|
} 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 {
|
} 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,
|
$condition,
|
||||||
['order' => ['id' => 'DESC'], 'limit' => $limit]
|
['order' => ['id' => 'DESC'], 'limit' => $limit]
|
||||||
);
|
);
|
||||||
|
|
||||||
$return = [];
|
$return = [];
|
||||||
foreach ($contacts as $contact) {
|
foreach ($intros as $intro) {
|
||||||
$account = Account::createFromContact($contact);
|
$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;
|
$return[] = $account;
|
||||||
}
|
}
|
||||||
|
@ -68,9 +109,9 @@ class FollowRequests extends Api
|
||||||
|
|
||||||
$links = [];
|
$links = [];
|
||||||
if ($count > $limit) {
|
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));
|
header('Link: ' . implode(', ', $links));
|
||||||
|
|
||||||
|
|
22
src/Module/Api/Mastodon/Instance.php
Normal file
22
src/Module/Api/Mastodon/Instance.php
Normal file
|
@ -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()));
|
||||||
|
}
|
||||||
|
}
|
36
src/Module/Api/Mastodon/Instance/Peers.php
Normal file
36
src/Module/Api/Mastodon/Instance/Peers.php
Normal file
|
@ -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
|
* @brief Login API user
|
||||||
*
|
*
|
||||||
|
* @return bool Was a user authenticated?
|
||||||
* @throws HTTPException\ForbiddenException
|
* @throws HTTPException\ForbiddenException
|
||||||
* @throws HTTPException\UnauthorizedException
|
* @throws HTTPException\UnauthorizedException
|
||||||
* @throws HTTPException\InternalServerErrorException
|
* @throws HTTPException\InternalServerErrorException
|
||||||
|
@ -69,6 +70,8 @@ class Api extends BaseModule
|
||||||
api_login(self::getApp());
|
api_login(self::getApp());
|
||||||
|
|
||||||
self::$current_user_id = api_user();
|
self::$current_user_id = api_user();
|
||||||
|
|
||||||
|
return (bool)self::$current_user_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -646,21 +646,25 @@ class Contact extends BaseModule
|
||||||
return $arr['output'];
|
return $arr['output'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$select_uid = local_user();
|
||||||
|
|
||||||
// @TODO: Replace with parameter from router
|
// @TODO: Replace with parameter from router
|
||||||
$type = $a->argv[1] ?? '';
|
$type = $a->argv[1] ?? '';
|
||||||
|
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
case 'blocked':
|
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;
|
break;
|
||||||
case 'hidden':
|
case 'hidden':
|
||||||
$sql_extra = " AND `hidden` AND NOT `blocked`";
|
$sql_extra = " AND `hidden` AND NOT `blocked` AND NOT `pending`";
|
||||||
break;
|
break;
|
||||||
case 'ignored':
|
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;
|
break;
|
||||||
case 'archived':
|
case 'archived':
|
||||||
$sql_extra = " AND `archive` AND NOT `blocked`";
|
$sql_extra = " AND `archive` AND NOT `blocked` AND NOT `pending`";
|
||||||
break;
|
break;
|
||||||
case 'pending':
|
case 'pending':
|
||||||
$sql_extra = sprintf(" AND `pending` AND NOT `archive` AND ((`rel` = %d)
|
$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_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`
|
$r = q("SELECT COUNT(*) AS `total` FROM `contact`
|
||||||
WHERE `uid` = %d AND `self` = 0 $sql_extra $sql_extra2 ",
|
WHERE `uid` = %d AND `self` = 0 $sql_extra $sql_extra2 $sql_extra3",
|
||||||
intval($_SESSION['uid'])
|
intval($select_uid)
|
||||||
);
|
);
|
||||||
if (DBA::isResult($r)) {
|
if (DBA::isResult($r)) {
|
||||||
$total = $r[0]['total'];
|
$total = $r[0]['total'];
|
||||||
}
|
}
|
||||||
$pager = new Pager($a->query_string);
|
$pager = new Pager($a->query_string);
|
||||||
|
|
||||||
$sql_extra3 = Widget::unavailableNetworks();
|
|
||||||
|
|
||||||
$contacts = [];
|
$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 ",
|
$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->getStart(),
|
||||||
$pager->getItemsPerPage()
|
$pager->getItemsPerPage()
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,17 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Friendica\Module;
|
namespace Friendica\Module;
|
||||||
|
|
||||||
use Friendica\App;
|
|
||||||
use Friendica\BaseModule;
|
use Friendica\BaseModule;
|
||||||
use Friendica\Core\L10n;
|
use Friendica\Core\L10n;
|
||||||
use Friendica\Core\Logger;
|
use Friendica\Model\Introduction;
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process follow request confirmations
|
* Process follow request confirmations
|
||||||
|
@ -30,67 +22,15 @@ class FollowConfirm extends BaseModule
|
||||||
|
|
||||||
$intro_id = intval($_POST['intro_id'] ?? 0);
|
$intro_id = intval($_POST['intro_id'] ?? 0);
|
||||||
$duplex = intval($_POST['duplex'] ?? 0);
|
$duplex = intval($_POST['duplex'] ?? 0);
|
||||||
$cid = intval($_POST['contact_id'] ?? 0);
|
|
||||||
$hidden = intval($_POST['hidden'] ?? 0);
|
$hidden = intval($_POST['hidden'] ?? 0);
|
||||||
|
|
||||||
if (empty($cid)) {
|
/** @var Introduction $Intro */
|
||||||
notice(L10n::t('No given contact.') . EOL);
|
$Intro = self::getClass(Introduction::class);
|
||||||
return;
|
$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]);
|
$Intro->confirm($duplex, $hidden);
|
||||||
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]);
|
|
||||||
|
|
||||||
$a->internalRedirect('contact/' . intval($cid));
|
$a->internalRedirect('contact/' . intval($cid));
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,15 +85,20 @@ class Compose extends BaseModule
|
||||||
$type = 'post';
|
$type = 'post';
|
||||||
$doesFederate = true;
|
$doesFederate = true;
|
||||||
|
|
||||||
if ($_REQUEST['contact_allow']
|
$contact_allow = $_REQUEST['contact_allow'] ?? '';
|
||||||
. $_REQUEST['group_allow']
|
$group_allow = $_REQUEST['group_allow'] ?? '';
|
||||||
. $_REQUEST['contact_deny']
|
$contact_deny = $_REQUEST['contact_deny'] ?? '';
|
||||||
. $_REQUEST['group_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']) : [];
|
$contact_allow_list = $contact_allow ? explode(',', $contact_allow) : [];
|
||||||
$group_allow_list = $_REQUEST['group_allow'] ? explode(',', $_REQUEST['group_allow']) : [];
|
$group_allow_list = $group_allow ? explode(',', $group_allow) : [];
|
||||||
$contact_deny_list = $_REQUEST['contact_deny'] ? explode(',', $_REQUEST['contact_deny']) : [];
|
$contact_deny_list = $contact_deny ? explode(',', $contact_deny) : [];
|
||||||
$group_deny_list = $_REQUEST['group_deny'] ? explode(',', $_REQUEST['group_deny']) : [];
|
$group_deny_list = $group_deny ? explode(',', $group_deny) : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -126,7 +126,7 @@ class NodeInfo extends BaseModule
|
||||||
$nodeinfo = [
|
$nodeinfo = [
|
||||||
'version' => '1.0',
|
'version' => '1.0',
|
||||||
'software' => [
|
'software' => [
|
||||||
'name' => 'friendica',
|
'name' => 'Friendica',
|
||||||
'version' => FRIENDICA_VERSION . '-' . DB_UPDATE_VERSION,
|
'version' => FRIENDICA_VERSION . '-' . DB_UPDATE_VERSION,
|
||||||
],
|
],
|
||||||
'protocols' => [
|
'protocols' => [
|
||||||
|
@ -191,7 +191,7 @@ class NodeInfo extends BaseModule
|
||||||
$nodeinfo = [
|
$nodeinfo = [
|
||||||
'version' => '2.0',
|
'version' => '2.0',
|
||||||
'software' => [
|
'software' => [
|
||||||
'name' => 'friendica',
|
'name' => 'Friendica',
|
||||||
'version' => FRIENDICA_VERSION . '-' . DB_UPDATE_VERSION,
|
'version' => FRIENDICA_VERSION . '-' . DB_UPDATE_VERSION,
|
||||||
],
|
],
|
||||||
'protocols' => ['dfrn', 'activitypub'],
|
'protocols' => ['dfrn', 'activitypub'],
|
||||||
|
|
|
@ -67,7 +67,7 @@ class Verify extends BaseModule
|
||||||
'$errors_label' => L10n::tt('Error', 'Errors', count(self::$errors)),
|
'$errors_label' => L10n::tt('Error', 'Errors', count(self::$errors)),
|
||||||
'$errors' => 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'),
|
'$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'),
|
'$verify_label' => L10n::t('Verify code and complete login'),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1574,7 +1574,7 @@ class Probe
|
||||||
$attr[$attribute->name] = trim($attribute->value);
|
$attr[$attribute->name] = trim($attribute->value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($feed_url == "") {
|
if (empty($feed_url) && !empty($attr['href'])) {
|
||||||
$feed_url = $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)
|
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
|
// 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']]);
|
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);
|
self::deliverDiaspora($cmd, $contact, $owner, $items, $target_item, $public_message, $top_level, $followup);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -562,7 +562,7 @@ class OnePoll
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decoding the header
|
// Decoding the header
|
||||||
$subject = imap_mime_header_decode($meta->subject);
|
$subject = imap_mime_header_decode($meta->subject ?? '');
|
||||||
$datarray['title'] = "";
|
$datarray['title'] = "";
|
||||||
foreach ($subject as $subpart) {
|
foreach ($subject as $subpart) {
|
||||||
if ($subpart->charset != "default") {
|
if ($subpart->charset != "default") {
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
use Friendica\Database\DBA;
|
use Friendica\Database\DBA;
|
||||||
|
|
||||||
if (!defined('DB_UPDATE_VERSION')) {
|
if (!defined('DB_UPDATE_VERSION')) {
|
||||||
define('DB_UPDATE_VERSION', 1324);
|
define('DB_UPDATE_VERSION', 1327);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
@ -102,6 +102,9 @@ return [
|
||||||
"pubkey" => ["type" => "text", "comment" => ""],
|
"pubkey" => ["type" => "text", "comment" => ""],
|
||||||
"baseurl" => ["type" => "varchar(255)", "comment" => "baseurl of the ap contact"],
|
"baseurl" => ["type" => "varchar(255)", "comment" => "baseurl of the ap contact"],
|
||||||
"generator" => ["type" => "varchar(255)", "comment" => "Name of the contact's system"],
|
"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" => ""]
|
"updated" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""]
|
||||||
],
|
],
|
||||||
"indexes" => [
|
"indexes" => [
|
||||||
|
@ -702,6 +705,7 @@ return [
|
||||||
"resource-id" => ["resource-id"],
|
"resource-id" => ["resource-id"],
|
||||||
"deleted_changed" => ["deleted", "changed"],
|
"deleted_changed" => ["deleted", "changed"],
|
||||||
"uid_wall_changed" => ["uid", "wall", "changed"],
|
"uid_wall_changed" => ["uid", "wall", "changed"],
|
||||||
|
"mention_uid_id" => ["mention", "uid", "id"],
|
||||||
"uid_eventid" => ["uid", "event-id"],
|
"uid_eventid" => ["uid", "event-id"],
|
||||||
"icid" => ["icid"],
|
"icid" => ["icid"],
|
||||||
"iaid" => ["iaid"],
|
"iaid" => ["iaid"],
|
||||||
|
|
|
@ -30,6 +30,9 @@ return [
|
||||||
'/api' => [
|
'/api' => [
|
||||||
'/v1' => [
|
'/v1' => [
|
||||||
'/follow_requests' => [Module\Api\Mastodon\FollowRequests::class, [R::GET ]],
|
'/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\Session\ISession;
|
||||||
use Friendica\Core\System;
|
use Friendica\Core\System;
|
||||||
use Friendica\Database\Database;
|
use Friendica\Database\Database;
|
||||||
|
use Friendica\Model\Contact;
|
||||||
use Friendica\Network\HTTPException;
|
use Friendica\Network\HTTPException;
|
||||||
use Friendica\Test\Util\Database\StaticDatabase;
|
use Friendica\Test\Util\Database\StaticDatabase;
|
||||||
use Monolog\Handler\TestHandler;
|
use Monolog\Handler\TestHandler;
|
||||||
|
@ -2929,8 +2930,8 @@ class ApiTest extends DatabaseTest
|
||||||
*/
|
*/
|
||||||
public function testApiFfIds()
|
public function testApiFfIds()
|
||||||
{
|
{
|
||||||
$result = api_ff_ids('json');
|
$result = api_ff_ids('json', Contact::FOLLOWER);
|
||||||
$this->assertNull($result);
|
$this->assertEquals(['id' => []], $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2952,7 +2953,7 @@ class ApiTest extends DatabaseTest
|
||||||
public function testApiFfIdsWithoutAuthenticatedUser()
|
public function testApiFfIdsWithoutAuthenticatedUser()
|
||||||
{
|
{
|
||||||
$_SESSION['authenticated'] = false;
|
$_SESSION['authenticated'] = false;
|
||||||
api_ff_ids('json');
|
api_ff_ids('json', Contact::FOLLOWER);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2963,7 +2964,7 @@ class ApiTest extends DatabaseTest
|
||||||
public function testApiFriendsIds()
|
public function testApiFriendsIds()
|
||||||
{
|
{
|
||||||
$result = api_friends_ids('json');
|
$result = api_friends_ids('json');
|
||||||
$this->assertNull($result);
|
$this->assertEquals(['id' => []], $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2974,7 +2975,7 @@ class ApiTest extends DatabaseTest
|
||||||
public function testApiFollowersIds()
|
public function testApiFollowersIds()
|
||||||
{
|
{
|
||||||
$result = api_followers_ids('json');
|
$result = api_followers_ids('json');
|
||||||
$this->assertNull($result);
|
$this->assertEquals(['id' => []], $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3723,28 +3724,6 @@ class ApiTest extends DatabaseTest
|
||||||
$this->markTestIncomplete();
|
$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.
|
* Test the api_in_reply_to() function.
|
||||||
*
|
*
|
||||||
|
|
|
@ -13,13 +13,13 @@
|
||||||
<whitelist>
|
<whitelist>
|
||||||
<directory suffix=".php">..</directory>
|
<directory suffix=".php">..</directory>
|
||||||
<exclude>
|
<exclude>
|
||||||
<directory suffix=".php">config/</directory>
|
<directory suffix=".php">../config</directory>
|
||||||
<directory suffix=".php">doc/</directory>
|
<directory suffix=".php">../doc</directory>
|
||||||
<directory suffix=".php">images/</directory>
|
<directory suffix=".php">../images</directory>
|
||||||
<directory suffix=".php">library/</directory>
|
<directory suffix=".php">../library</directory>
|
||||||
<directory suffix=".php">spec/</directory>
|
<directory suffix=".php">../spec</directory>
|
||||||
<directory suffix=".php">tests/</directory>
|
<directory suffix=".php">../tests</directory>
|
||||||
<directory suffix=".php">view/</directory>
|
<directory suffix=".php">../view</directory>
|
||||||
</exclude>
|
</exclude>
|
||||||
</whitelist>
|
</whitelist>
|
||||||
</filter>
|
</filter>
|
||||||
|
|
12
update.php
12
update.php
|
@ -396,3 +396,15 @@ function update_1323()
|
||||||
return Update::SUCCESS;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
15285
view/lang/de/messages.po
15285
view/lang/de/messages.po
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
17587
view/lang/et/messages.po
17587
view/lang/et/messages.po
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
16456
view/lang/ja/messages.po
16456
view/lang/ja/messages.po
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
16514
view/lang/nl/messages.po
16514
view/lang/nl/messages.po
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
17081
view/lang/pl/messages.po
17081
view/lang/pl/messages.po
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -145,6 +145,15 @@
|
||||||
identify: function(obj) { return obj.type + '-' + obj.id.toString(); },
|
identify: function(obj) { return obj.type + '-' + obj.id.toString(); },
|
||||||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace(['name', 'addr']),
|
datumTokenizer: Bloodhound.tokenizers.obj.whitespace(['name', 'addr']),
|
||||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
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();
|
acl.initialize();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue