Merge remote-tracking branch 'upstream/develop' into fetch-usage

This commit is contained in:
Michael 2022-03-19 11:21:38 +00:00
commit 4e45987f74
196 changed files with 19366 additions and 17273 deletions

View File

@ -6,6 +6,7 @@ AddType application/x-java-archive .jar
AddType audio/ogg .oga AddType audio/ogg .oga
#AddHandler php53-cgi .php #AddHandler php53-cgi .php
# deny access to log files (friendica.log or php.out)
<FilesMatch "\.(out|log)$"> <FilesMatch "\.(out|log)$">
<IfModule authz_host_module> <IfModule authz_host_module>
#Apache 2.4 #Apache 2.4
@ -17,6 +18,18 @@ AddType audio/ogg .oga
</IfModule> </IfModule>
</FilesMatch> </FilesMatch>
# deny access to backup files
<FilesMatch "(\~|\.bak|\.swp)$">
<IfModule authz_host_module>
#Apache 2.4
Require all denied
</IfModule>
<IfModule !authz_host_module>
#Apache 2.2
Deny from all
</IfModule>
</FilesMatch>
<IfModule mod_rewrite.c> <IfModule mod_rewrite.c>
RewriteEngine on RewriteEngine on
# Protect repository directory from browsing # Protect repository directory from browsing

View File

@ -62,7 +62,7 @@ pipeline:
- export RELEASE="friendica-full-$VERSION" - export RELEASE="friendica-full-$VERSION"
- export ARTIFACT="$RELEASE.tar.gz" - export ARTIFACT="$RELEASE.tar.gz"
- tar - tar
--transform "s,^,$RELEASE/," --transform "s,^,$RELEASE/,S"
-X mods/release-list-exclude.txt -X mods/release-list-exclude.txt
-T mods/release-list-include.txt -T mods/release-list-include.txt
-cvzf ./build/$ARTIFACT -cvzf ./build/$ARTIFACT

View File

@ -60,7 +60,7 @@ pipeline:
- export RELEASE="friendica-full-$VERSION" - export RELEASE="friendica-full-$VERSION"
- export ARTIFACT="$RELEASE.tar.gz" - export ARTIFACT="$RELEASE.tar.gz"
- tar - tar
--transform "s,^,$RELEASE/," --transform "s,^,$RELEASE/,S"
-X mods/release-list-exclude.txt -X mods/release-list-exclude.txt
-T mods/release-list-include.txt -T mods/release-list-include.txt
-cvzf ./build/$ARTIFACT -cvzf ./build/$ARTIFACT

View File

@ -1,3 +1,39 @@
Version 2022.05 (unreleased)
Friendica Core
Friendica Addons
Closed Issues
Version 2022.03 (2022-03-07)
Friendica Core
Updates to the translations AR, DE, HU [translation teams]
Updates to the documentation [bkil, tobiasd]
General code cleanup [annando, MrPetovan]
Enhanced the federation statistics page in the admin panel [annando]
Enhanced handling of database errors [annando]
Enhanced the thread completion [annando, MrPetovan]
Enhanced the handling of unfollow/revoke actions [MrPetovan]
Enhanced the API [annando]
Fixed a bug that caused wrong categories were displayed in a users profile [MrPetovan]
Fixed a bug that lead to private messages being send to the wrong recipient [MrPetovan]
Added display of post receivers [annando]
Added pleroma like version to the API results [MrPetovan]
Added advanced configuration option to automatically re-use the abstract field from AP conversations [annando]
Switched to SMARTY-4 templating engine [MrPetovan]
Breaking: The distribution of _private forums_ was moved to ActivityPub,
making them incompatible with older versions of Friendica [annando]
Breaking: The Twitter-/Friendica-/Statusnet-API now uses the same base
for the id as the Mastodon API (uri-id instead of id). To still
receive new posts with (for example) Friendiqa you have to remove
the account and add it again. [annando]
Friendica Addons
Added S3 Storage Backend addon [nupplaphil]
Closed Issues
11220, 11222, 11232, 11234, 11248, 11245, 11264, 11274
Version 2022.02 (2022-02-06) Version 2022.02 (2022-02-06)
Friendica Core Friendica Core
Updates to the translations AR, DE, ET, FR, GB_EN, GB_US, HU, IT, RU, SV [translation teams] Updates to the translations AR, DE, ET, FR, GB_EN, GB_US, HU, IT, RU, SV [translation teams]

View File

@ -45,6 +45,7 @@ ben-utzer
Beringer Zsolt Beringer Zsolt
BinkaDroid BinkaDroid
Bjoessi Bjoessi
bkil
bob lebonche bob lebonche
Boris Daniel Martinez Millàn Boris Daniel Martinez Millàn
bufalo1973 bufalo1973
@ -143,6 +144,7 @@ Josef Moravek
juanman juanman
julia.domagalska julia.domagalska
Julio Cova Julio Cova
k-alin
Karel Karel
Karolina Karolina
Kastal András Kastal András

View File

@ -51,7 +51,7 @@
"pragmarx/recovery": "^0.2", "pragmarx/recovery": "^0.2",
"psr/container": "^1.0", "psr/container": "^1.0",
"seld/cli-prompt": "^1.0", "seld/cli-prompt": "^1.0",
"smarty/smarty": "^3.1", "smarty/smarty": "^4",
"ua-parser/uap-php": "^3.9", "ua-parser/uap-php": "^3.9",
"xemlock/htmlpurifier-html5": "^0.1.11", "xemlock/htmlpurifier-html5": "^0.1.11",
"fxp/composer-asset-plugin": "^1.4", "fxp/composer-asset-plugin": "^1.4",

108
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "3d221e30c9cb7e3f34d8d8141b6fea6c", "content-hash": "f5922f03b367e68a5930df6ed80c5c2f",
"packages": [ "packages": [
{ {
"name": "asika/simple-console", "name": "asika/simple-console",
@ -1152,6 +1152,24 @@
"html", "html",
"markdown" "markdown"
], ],
"funding": [
{
"url": "https://www.colinodell.com/sponsor",
"type": "custom"
},
{
"url": "https://www.paypal.me/colinpodell/10.00",
"type": "custom"
},
{
"url": "https://github.com/colinodell",
"type": "github"
},
{
"url": "https://www.patreon.com/colinodell",
"type": "patreon"
}
],
"time": "2020-07-01T00:34:03+00:00" "time": "2020-07-01T00:34:03+00:00"
}, },
{ {
@ -1481,6 +1499,12 @@
"mobile detector", "mobile detector",
"php mobile detect" "php mobile detect"
], ],
"funding": [
{
"url": "https://github.com/serbanghita",
"type": "github"
}
],
"time": "2021-02-19T21:22:57+00:00" "time": "2021-02-19T21:22:57+00:00"
}, },
{ {
@ -1553,6 +1577,16 @@
"logging", "logging",
"psr-3" "psr-3"
], ],
"funding": [
{
"url": "https://github.com/Seldaek",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
"type": "tidelift"
}
],
"time": "2021-05-28T08:32:12+00:00" "time": "2021-05-28T08:32:12+00:00"
}, },
{ {
@ -3647,29 +3681,29 @@
}, },
{ {
"name": "smarty/smarty", "name": "smarty/smarty",
"version": "v3.1.43", "version": "v4.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/smarty-php/smarty.git", "url": "https://github.com/smarty-php/smarty.git",
"reference": "273f7e00fec034f6d61112552e9caf08d19565b7" "reference": "9e0536de18b53ba193364291ef0303b0ab9903e1"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/smarty-php/smarty/zipball/273f7e00fec034f6d61112552e9caf08d19565b7", "url": "https://api.github.com/repos/smarty-php/smarty/zipball/9e0536de18b53ba193364291ef0303b0ab9903e1",
"reference": "273f7e00fec034f6d61112552e9caf08d19565b7", "reference": "9e0536de18b53ba193364291ef0303b0ab9903e1",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.2" "php": "^7.1 || ^8.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^7.5 || ^6.5 || ^5.7 || ^4.8", "phpunit/phpunit": "^8.5 || ^7.5",
"smarty/smarty-lexer": "^3.1" "smarty/smarty-lexer": "^3.1"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "3.1.x-dev" "dev-master": "4.0.x-dev"
} }
}, },
"autoload": { "autoload": {
@ -3693,14 +3727,18 @@
{ {
"name": "Rodney Rehm", "name": "Rodney Rehm",
"email": "rodney.rehm@medialize.de" "email": "rodney.rehm@medialize.de"
},
{
"name": "Simon Wisselink",
"homepage": "https://www.iwink.nl/"
} }
], ],
"description": "Smarty - the compiling PHP template engine", "description": "Smarty - the compiling PHP template engine",
"homepage": "http://www.smarty.net", "homepage": "https://smarty-php.github.io/smarty/",
"keywords": [ "keywords": [
"templating" "templating"
], ],
"time": "2022-01-10T09:52:40+00:00" "time": "2022-02-06T20:34:27+00:00"
}, },
{ {
"name": "spomky-labs/base64url", "name": "spomky-labs/base64url",
@ -3751,6 +3789,16 @@
"safe", "safe",
"url" "url"
], ],
"funding": [
{
"url": "https://github.com/Spomky",
"type": "github"
},
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"time": "2020-11-03T09:10:25+00:00" "time": "2020-11-03T09:10:25+00:00"
}, },
{ {
@ -4613,6 +4661,20 @@
"constructor", "constructor",
"instantiate" "instantiate"
], ],
"funding": [
{
"url": "https://www.doctrine-project.org/sponsorship.html",
"type": "custom"
},
{
"url": "https://www.patreon.com/phpdoctrine",
"type": "patreon"
},
{
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator",
"type": "tidelift"
}
],
"time": "2020-11-10T18:47:58+00:00" "time": "2020-11-10T18:47:58+00:00"
}, },
{ {
@ -4822,6 +4884,12 @@
"object", "object",
"object graph" "object graph"
], ],
"funding": [
{
"url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
"type": "tidelift"
}
],
"time": "2020-11-13T09:40:50+00:00" "time": "2020-11-13T09:40:50+00:00"
}, },
{ {
@ -6547,6 +6615,20 @@
"polyfill", "polyfill",
"portable" "portable"
], ],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2021-02-19T12:13:01+00:00" "time": "2021-02-19T12:13:01+00:00"
}, },
{ {
@ -6587,6 +6669,12 @@
} }
], ],
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
"funding": [
{
"url": "https://github.com/theseer",
"type": "github"
}
],
"time": "2021-07-28T10:34:58+00:00" "time": "2021-07-28T10:34:58+00:00"
}, },
{ {

View File

@ -1,6 +1,6 @@
-- ------------------------------------------ -- ------------------------------------------
-- Friendica 2022.05-dev (Siberian Iris) -- Friendica 2022.05-dev (Siberian Iris)
-- DB_UPDATE_VERSION 1450 -- DB_UPDATE_VERSION 1452
-- ------------------------------------------ -- ------------------------------------------
@ -644,10 +644,13 @@ CREATE TABLE IF NOT EXISTS `group` (
`uid` mediumint unsigned NOT NULL DEFAULT 0 COMMENT 'Owner User id', `uid` mediumint unsigned NOT NULL DEFAULT 0 COMMENT 'Owner User id',
`visible` boolean NOT NULL DEFAULT '0' COMMENT '1 indicates the member list is not private', `visible` boolean NOT NULL DEFAULT '0' COMMENT '1 indicates the member list is not private',
`deleted` boolean NOT NULL DEFAULT '0' COMMENT '1 indicates the group has been deleted', `deleted` boolean NOT NULL DEFAULT '0' COMMENT '1 indicates the group has been deleted',
`cid` int unsigned COMMENT 'Contact id of forum. When this field is filled then the members are synced automatically.',
`name` varchar(255) NOT NULL DEFAULT '' COMMENT 'human readable name of group', `name` varchar(255) NOT NULL DEFAULT '' COMMENT 'human readable name of group',
PRIMARY KEY(`id`), PRIMARY KEY(`id`),
INDEX `uid` (`uid`), INDEX `uid` (`uid`),
FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE INDEX `cid` (`cid`),
FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`cid`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='privacy groups, group info'; ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='privacy groups, group info';
-- --
@ -879,7 +882,7 @@ CREATE TABLE IF NOT EXISTS `notify` (
FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE, FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE, FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`parent-uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE FOREIGN KEY (`parent-uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='notifications'; ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='[Deprecated] User notifications';
-- --
-- TABLE notify-threads -- TABLE notify-threads
@ -1274,7 +1277,7 @@ CREATE TABLE IF NOT EXISTS `post-thread-user` (
`wall` boolean NOT NULL DEFAULT '0' COMMENT 'This item was posted to the wall of uid', `wall` boolean NOT NULL DEFAULT '0' COMMENT 'This item was posted to the wall of uid',
`mention` boolean NOT NULL DEFAULT '0' COMMENT '', `mention` boolean NOT NULL DEFAULT '0' COMMENT '',
`pubmail` boolean NOT NULL DEFAULT '0' COMMENT '', `pubmail` boolean NOT NULL DEFAULT '0' COMMENT '',
`forum_mode` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '', `forum_mode` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'Deprecated',
`contact-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'contact.id', `contact-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'contact.id',
`unseen` boolean NOT NULL DEFAULT '1' COMMENT 'post has not been seen', `unseen` boolean NOT NULL DEFAULT '1' COMMENT 'post has not been seen',
`hidden` boolean NOT NULL DEFAULT '0' COMMENT 'Marker to hide the post from the user', `hidden` boolean NOT NULL DEFAULT '0' COMMENT 'Marker to hide the post from the user',
@ -1609,7 +1612,6 @@ CREATE VIEW `post-user-view` AS SELECT
`post-user`.`deleted` AS `deleted`, `post-user`.`deleted` AS `deleted`,
`post-user`.`origin` AS `origin`, `post-user`.`origin` AS `origin`,
`post-thread-user`.`origin` AS `parent-origin`, `post-thread-user`.`origin` AS `parent-origin`,
`post-thread-user`.`forum_mode` AS `forum_mode`,
`post-thread-user`.`mention` AS `mention`, `post-thread-user`.`mention` AS `mention`,
`post-user`.`global` AS `global`, `post-user`.`global` AS `global`,
`post-user`.`network` AS `network`, `post-user`.`network` AS `network`,
@ -1770,7 +1772,6 @@ CREATE VIEW `post-thread-user-view` AS SELECT
`post-thread-user`.`unseen` AS `unseen`, `post-thread-user`.`unseen` AS `unseen`,
`post-user`.`deleted` AS `deleted`, `post-user`.`deleted` AS `deleted`,
`post-thread-user`.`origin` AS `origin`, `post-thread-user`.`origin` AS `origin`,
`post-thread-user`.`forum_mode` AS `forum_mode`,
`post-thread-user`.`mention` AS `mention`, `post-thread-user`.`mention` AS `mention`,
`post-user`.`global` AS `global`, `post-user`.`global` AS `global`,
`post-thread-user`.`network` AS `network`, `post-thread-user`.`network` AS `network`,

View File

@ -74,7 +74,7 @@ These endpoints use the [Mastodon API entities](https://docs.joinmastodon.org/en
- [`GET /api/v1/instance`](https://docs.joinmastodon.org/methods/instance#fetch-instance) - [`GET /api/v1/instance`](https://docs.joinmastodon.org/methods/instance#fetch-instance)
- GET /api/v1/instance/rules Undocumented, returns Terms of Service - `GET /api/v1/instance/rules` Undocumented, returns Terms of Service
- [`GET /api/v1/instance/peers`](https://docs.joinmastodon.org/methods/instance#list-of-connected-domains) - [`GET /api/v1/instance/peers`](https://docs.joinmastodon.org/methods/instance#list-of-connected-domains)
- [`GET /api/v1/lists`](https://docs.joinmastodon.org/methods/timelines/lists/) - [`GET /api/v1/lists`](https://docs.joinmastodon.org/methods/timelines/lists/)
- [`POST /api/v1/lists`](https://docs.joinmastodon.org/methods/timelines/lists/) - [`POST /api/v1/lists`](https://docs.joinmastodon.org/methods/timelines/lists/)
@ -102,6 +102,7 @@ These endpoints use the [Mastodon API entities](https://docs.joinmastodon.org/en
- [`GET /api/v1/scheduled_statuses/:id`](https://docs.joinmastodon.org/methods/statuses/scheduled_statuses/) - [`GET /api/v1/scheduled_statuses/:id`](https://docs.joinmastodon.org/methods/statuses/scheduled_statuses/)
- [`GET /api/v1/search`](https://docs.joinmastodon.org/methods/search/) - [`GET /api/v1/search`](https://docs.joinmastodon.org/methods/search/)
- [`POST /api/v1/statuses`](https://docs.joinmastodon.org/methods/statuses/) - [`POST /api/v1/statuses`](https://docs.joinmastodon.org/methods/statuses/)
- Additionally to the static values `public`, `unlisted` and `private`, the `visibility` parameter can contain a numeric value with a group id.
- [`GET /api/v1/statuses/:id`](https://docs.joinmastodon.org/methods/statuses/) - [`GET /api/v1/statuses/:id`](https://docs.joinmastodon.org/methods/statuses/)
- [`DELETE /api/v1/statuses/:id`](https://docs.joinmastodon.org/methods/statuses/) - [`DELETE /api/v1/statuses/:id`](https://docs.joinmastodon.org/methods/statuses/)
- [`GET /api/v1/statuses/:id/card`](https://docs.joinmastodon.org/methods/statuses/) - [`GET /api/v1/statuses/:id/card`](https://docs.joinmastodon.org/methods/statuses/)
@ -150,7 +151,7 @@ They refer to features that don't exist in Friendica yet.
These endpoints won't be implemented at the moment. These endpoints won't be implemented at the moment.
They refer to features or data that don't exist in Friendica yet. They refer to features or data that don't exist in Friendica yet.
- POST /api/meta Misskey API endpoint. - `POST /api/meta` Misskey API endpoint.
- [`POST /api/v1/accounts`](https://docs.joinmastodon.org/methods/accounts/) - [`POST /api/v1/accounts`](https://docs.joinmastodon.org/methods/accounts/)
- [`GET /api/v1/accounts/:id/featured_tags`](https://docs.joinmastodon.org/methods/accounts/) - [`GET /api/v1/accounts/:id/featured_tags`](https://docs.joinmastodon.org/methods/accounts/)
- [`POST /api/v1/accounts/:id/pin`](https://docs.joinmastodon.org/methods/accounts/) - [`POST /api/v1/accounts/:id/pin`](https://docs.joinmastodon.org/methods/accounts/)

View File

@ -626,7 +626,8 @@ Hook data:
Called when unfollowing a remote contact on a non-native network (like Twitter) Called when unfollowing a remote contact on a non-native network (like Twitter)
Hook data: Hook data:
- **contact** (input): the remote contact (uid = local unfollowing user id) array. - **contact** (input): the target public contact (uid = 0) array.
- **uid** (input): the id of the source local user.
- **result** (output): wether the unfollowing is successful or not. - **result** (output): wether the unfollowing is successful or not.
### revoke_follow ### revoke_follow
@ -634,7 +635,8 @@ Hook data:
Called when making a remote contact on a non-native network (like Twitter) unfollow you. Called when making a remote contact on a non-native network (like Twitter) unfollow you.
Hook data: Hook data:
- **contact** (input): the remote contact (uid = local revoking user id) array. - **contact** (input): the target public contact (uid = 0) array.
- **uid** (input): the id of the source local user.
- **result** (output): a boolean value indicating wether the operation was successful or not. - **result** (output): a boolean value indicating wether the operation was successful or not.
### block ### block
@ -717,10 +719,6 @@ Here is a complete list of all hook callbacks with file locations (as of 24-Sep-
Hook::callAll('personal_xrd', $arr); Hook::callAll('personal_xrd', $arr);
### mod/ping.php
Hook::callAll('network_ping', $arr);
### mod/parse_url.php ### mod/parse_url.php
Hook::callAll("parse_link", $arr); Hook::callAll("parse_link", $arr);
@ -863,6 +861,10 @@ Here is a complete list of all hook callbacks with file locations (as of 24-Sep-
Hook::callAll('register_account', $uid); Hook::callAll('register_account', $uid);
Hook::callAll('remove_user', $user); Hook::callAll('remove_user', $user);
### src/Module/Notifications/Ping.php
Hook::callAll('network_ping', $arr);
### src/Module/PermissionTooltip.php ### src/Module/PermissionTooltip.php
Hook::callAll('lockview_content', $item); Hook::callAll('lockview_content', $item);

View File

@ -3,7 +3,7 @@
* [Home](help) * [Home](help)
In addition to [move your account](help/Move-Account) you can export and import the list of accounts you follow. In addition to [move your account](help/Move-Account) you can export and import the list of accounts you follow.
The exported list is stored as CSV file that is compatible to the format used by other platforms as e.g. Mastodon or Pleroma. The exported list is stored as CSV file that is compatible to the format used by other platforms as e.g. Mastodon, Misskey or Pleroma.
## Export of followed Contacts ## Export of followed Contacts

View File

@ -449,3 +449,58 @@ section:
sql_mode = ''; sql_mode = '';
After that, restart mysql and try again. After that, restart mysql and try again.
### Your worker never or rarely runs
Friendica is coded to always play nice. It checks whether the host machine is idle enough and if it _seems_ to be overloaded, it intermittently refuses to process the worker queue.
Such checks originate from the days of single-user single-core machines and involves thresholds that you should adjust based on the number of exclusive CPU cores you have. See this issue for more information:
* https://github.com/friendica/friendica/issues/10131
If you want to be neighborly and are using a shared web hosting PaaS provider, especially within the free tier, you need to set `maxloadavg` to say twice the maximum value of `/proc/loadavg` during peak hours.
If you have the whole (virtual) machine for yourself such as in case of an IaaS VPS, you can set it to orders of magnitude higher than its commonly observed value, such as 1000.
You should instead enact limits in your web server configuration based on the number of entry processes to cap the concurrent memory usage of your PHP processes.
See `RLimitMEM`, `RLimitCPU`, `RLimitNPROC`, `StartServers`, `ServerLimit`, `MaxRequestsPerChild`, `pm.max_children`, `pm.start_servers` and related options in your server.
### Error uploading even small image files
You tried to upload an image up to 100kB and it failed.
You may not have the ownership or file mode set correctly if you are using the file system storage backend.
Change the backend to database. If this solves it, that is what needs to be fixed.
### Error uploading large files
You may find `413 Request Entity Too Large` or `500 Internal Error` in the network inspector of the browser if the file is too large, for example if it is a video.
First try to upload a very small file, up to 100kB. If that succeeds, you will need to increase limits at multiple places, including on any web proxy that you are using.
In your PHP ini:
* `upload_max_filesize`: defaults to 2MB
* `post_max_size`: defaults to 8MB, must be greater than `upload_max_filesize`
* `memory_limit`: defaults to 128MB, must be greater than `post_max_size`
You should verify whether you changed them in the _right file_ by checking the web interface at the end of the overview on the `Admin` panel.
For Apache2:
* `LimitRequestBody`: defaults to unlimited
* `SSLRenegBufferSize`: defaults to 128kB, only if your site uses TLS and perhaps only when using `SSLVerifyClient` or `SSLVerifyDepth`
For nginx:
* `client_max_body_size`: defaults to 1MB
If you are using the database backend for storage, increase this in your SQL configuration:
* `max_allowed_packet`: defaults to 32MB
If you use the ModSecurity WAF:
* `SecRequestBodyLimit`: defaults to 12MB
* `SecRequestBodyNoFilesLimit`: defaults to 128kB, should not apply to Friendica

View File

@ -37,7 +37,7 @@ Database Tables
| [mailacct](help/database/db_mailacct) | Mail account data for fetching mails | | [mailacct](help/database/db_mailacct) | Mail account data for fetching mails |
| [manage](help/database/db_manage) | table of accounts that can manage each other | | [manage](help/database/db_manage) | table of accounts that can manage each other |
| [notification](help/database/db_notification) | notifications | | [notification](help/database/db_notification) | notifications |
| [notify](help/database/db_notify) | notifications | | [notify](help/database/db_notify) | [Deprecated] User notifications |
| [notify-threads](help/database/db_notify-threads) | | | [notify-threads](help/database/db_notify-threads) | |
| [oembed](help/database/db_oembed) | cache for OEmbed queries | | [oembed](help/database/db_oembed) | cache for OEmbed queries |
| [openwebauth-token](help/database/db_openwebauth-token) | Store OpenWebAuth token to verify contacts | | [openwebauth-token](help/database/db_openwebauth-token) | Store OpenWebAuth token to verify contacts |

View File

@ -6,13 +6,14 @@ privacy groups, group info
Fields Fields
------ ------
| Field | Description | Type | Null | Key | Default | Extra | | Field | Description | Type | Null | Key | Default | Extra |
| ------- | ------------------------------------------ | ------------------ | ---- | --- | ------- | -------------- | | ------- | ----------------------------------------------------------------------------------------- | ------------------ | ---- | --- | ------- | -------------- |
| id | sequential ID | int unsigned | NO | PRI | NULL | auto_increment | | id | sequential ID | int unsigned | NO | PRI | NULL | auto_increment |
| uid | Owner User id | mediumint unsigned | NO | | 0 | | | uid | Owner User id | mediumint unsigned | NO | | 0 | |
| visible | 1 indicates the member list is not private | boolean | NO | | 0 | | | visible | 1 indicates the member list is not private | boolean | NO | | 0 | |
| deleted | 1 indicates the group has been deleted | boolean | NO | | 0 | | | deleted | 1 indicates the group has been deleted | boolean | NO | | 0 | |
| name | human readable name of group | varchar(255) | NO | | | | | cid | Contact id of forum. When this field is filled then the members are synced automatically. | int unsigned | YES | | NULL | |
| name | human readable name of group | varchar(255) | NO | | | |
Indexes Indexes
------------ ------------
@ -21,6 +22,7 @@ Indexes
| ------- | ------ | | ------- | ------ |
| PRIMARY | id | | PRIMARY | id |
| uid | uid | | uid | uid |
| cid | cid |
Foreign Keys Foreign Keys
------------ ------------
@ -28,5 +30,6 @@ Foreign Keys
| Field | Target Table | Target Field | | Field | Target Table | Target Field |
|-------|--------------|--------------| |-------|--------------|--------------|
| uid | [user](help/database/db_user) | uid | | uid | [user](help/database/db_user) | uid |
| cid | [contact](help/database/db_contact) | id |
Return to [database documentation](help/database) Return to [database documentation](help/database)

View File

@ -1,7 +1,7 @@
Table notify Table notify
=========== ===========
notifications [Deprecated] User notifications
Fields Fields
------ ------

View File

@ -24,7 +24,7 @@ Fields
| wall | This item was posted to the wall of uid | boolean | NO | | 0 | | | wall | This item was posted to the wall of uid | boolean | NO | | 0 | |
| mention | | boolean | NO | | 0 | | | mention | | boolean | NO | | 0 | |
| pubmail | | boolean | NO | | 0 | | | pubmail | | boolean | NO | | 0 | |
| forum_mode | | tinyint unsigned | NO | | 0 | | | forum_mode | Deprecated | tinyint unsigned | NO | | 0 | |
| contact-id | contact.id | int unsigned | NO | | 0 | | | contact-id | contact.id | int unsigned | NO | | 0 | |
| unseen | post has not been seen | boolean | NO | | 1 | | | unseen | post has not been seen | boolean | NO | | 1 | |
| hidden | Marker to hide the post from the user | boolean | NO | | 0 | | | hidden | Marker to hide the post from the user | boolean | NO | | 0 | |

View File

@ -236,10 +236,6 @@ Eine komplette Liste aller Hook-Callbacks mit den zugehörigen Dateien (am 01-Ap
Hook::callAll('personal_xrd', $arr); Hook::callAll('personal_xrd', $arr);
### mod/ping.php
Hook::callAll('network_ping', $arr);
### mod/parse_url.php ### mod/parse_url.php
Hook::callAll("parse_link", $arr); Hook::callAll("parse_link", $arr);
@ -426,6 +422,10 @@ Eine komplette Liste aller Hook-Callbacks mit den zugehörigen Dateien (am 01-Ap
Hook::callAll('storage_instance', $data); Hook::callAll('storage_instance', $data);
Hook::callAll('storage_config', $data); Hook::callAll('storage_config', $data);
### src/Module/Notifications/Ping.php
Hook::callAll('network_ping', $arr);
### src/Module/PermissionTooltip.php ### src/Module/PermissionTooltip.php
Hook::callAll('lockview_content', $item); Hook::callAll('lockview_content', $item);

View File

@ -3,7 +3,7 @@
* [Home](help) * [Home](help)
Zusätzlich zum [Umziehen des Accounts](help/Move-Account) kannst du die Liste der von dir gefolgten Kontakte exportieren und importieren. Zusätzlich zum [Umziehen des Accounts](help/Move-Account) kannst du die Liste der von dir gefolgten Kontakte exportieren und importieren.
Die exportierte Liste wird als CSV Datei in einem zu anderen Plattformen, z.B. Mastodon oder Pleroma, kompatiblen Format gespeichert. Die exportierte Liste wird als CSV Datei in einem zu anderen Plattformen, z.B. Mastodon, Misskey oder Pleroma, kompatiblen Format gespeichert.
## Export der gefolgten Kontakte ## Export der gefolgten Kontakte

View File

@ -23,7 +23,6 @@ use Friendica\App;
use Friendica\Content\Text\BBCode; use Friendica\Content\Text\BBCode;
use Friendica\Content\Widget; use Friendica\Content\Widget;
use Friendica\Core\Logger; use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\Renderer; use Friendica\Core\Renderer;
use Friendica\Core\Session; use Friendica\Core\Session;
use Friendica\Database\DBA; use Friendica\Database\DBA;
@ -36,6 +35,7 @@ use Friendica\Module\ActivityPub\Objects;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
use Friendica\Protocol\ActivityPub; use Friendica\Protocol\ActivityPub;
use Friendica\Protocol\DFRN; use Friendica\Protocol\DFRN;
use Friendica\Protocol\Diaspora;
function display_init(App $a) function display_init(App $a)
{ {
@ -108,55 +108,23 @@ function display_init(App $a)
$item = $parent ?: $item; $item = $parent ?: $item;
} }
$profiledata = display_fetchauthor($item); DI::page()['aside'] = Widget\VCard::getHTML(display_fetchauthor($item));
DI::page()['aside'] = Widget\VCard::getHTML($profiledata);
} }
function display_fetchauthor($item) function display_fetchauthor($item)
{ {
$profiledata = Contact::getByURLForUser($item['author-link'], local_user()); if (Diaspora::isReshare($item['body'], true)) {
$shared = Item::getShareArray($item);
// Check for a repeated message
$shared = Item::getShareArray($item);
if (!empty($shared) && empty($shared['comment'])) {
$profiledata = [
'uid' => 0,
'id' => -1,
'nickname' => '',
'name' => '',
'picdate' => '',
'photo' => '',
'url' => '',
'network' => '',
];
if (!empty($shared['author'])) {
$profiledata['name'] = $shared['author'];
}
if (!empty($shared['profile'])) { if (!empty($shared['profile'])) {
$profiledata['url'] = $shared['profile']; $contact = Contact::getByURLForUser($shared['profile'], local_user());
} }
if (!empty($shared['avatar'])) {
$profiledata['photo'] = $shared['avatar'];
}
$profiledata['nickname'] = $profiledata['name'];
$profiledata['network'] = Protocol::PHANTOM;
$profiledata['address'] = '';
$profiledata['about'] = '';
$profiledata = Contact::getByURLForUser($profiledata['url'], local_user()) ?: $profiledata;
} }
if (!empty($profiledata['photo'])) { if (empty($contact)) {
$profiledata['photo'] = DI::baseUrl()->remove($profiledata['photo']); $contact = Contact::getById($item['author-id']);
} }
return $profiledata; return $contact;
} }
function display_content(App $a, $update = false, $update_uid = 0) function display_content(App $a, $update = false, $update_uid = 0)

View File

@ -29,7 +29,6 @@
*/ */
use Friendica\App; use Friendica\App;
use Friendica\Content\Item as ItemHelper;
use Friendica\Content\PageInfo; use Friendica\Content\PageInfo;
use Friendica\Content\Text\BBCode; use Friendica\Content\Text\BBCode;
use Friendica\Core\Hook; use Friendica\Core\Hook;
@ -40,11 +39,11 @@ use Friendica\Core\System;
use Friendica\Core\Worker; use Friendica\Core\Worker;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\APContact;
use Friendica\Model\Attach; use Friendica\Model\Attach;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Friendica\Model\Conversation; use Friendica\Model\Conversation;
use Friendica\Model\FileTag; use Friendica\Model\FileTag;
use Friendica\Model\Group;
use Friendica\Model\Item; use Friendica\Model\Item;
use Friendica\Model\ItemURI; use Friendica\Model\ItemURI;
use Friendica\Model\Notification; use Friendica\Model\Notification;
@ -384,80 +383,34 @@ function item_post(App $a) {
$contact_record = DBA::selectFirst('contact', [], ['uid' => $profile_uid, 'self' => true]) ?: []; $contact_record = DBA::selectFirst('contact', [], ['uid' => $profile_uid, 'self' => true]) ?: [];
} }
// Look for any tags and linkify them
$inform = '';
$private_forum = false;
$private_id = null;
$only_to_forum = false;
$forum_contact = [];
// Personal notes must never be altered to a forum post. // Personal notes must never be altered to a forum post.
if ($posttype != Item::PT_PERSONAL_NOTE) { if ($posttype != Item::PT_PERSONAL_NOTE) {
// Convert mentions in the body to a unified format // Look for any tags and linkify them
$body = BBCode::setMentions($body, local_user() ? local_user() : $profile_uid, $network); $item = [
'uid' => local_user() ? local_user() : $profile_uid,
'gravity' => $toplevel_item_id ? GRAVITY_COMMENT : GRAVITY_PARENT,
'network' => $network,
'body' => $body,
'postopts' => $postopts,
'private' => $private,
'allow_cid' => $str_contact_allow,
'allow_gid' => $str_group_allow,
'deny_cid' => $str_contact_deny,
'deny_gid' => $str_group_deny,
];
// Search for forum mentions $item = DI::contentItem()->expandTags($item);
foreach (Tag::getFromBody($body, Tag::TAG_CHARACTER[Tag::MENTION] . Tag::TAG_CHARACTER[Tag::EXCLUSIVE_MENTION]) as $tag) {
$contact = Contact::getByURLForUser($tag[2], $profile_uid);
if (!empty($inform)) {
$inform .= ',';
}
$inform .= 'cid:' . $contact['id'];
if (!$toplevel_item_id || empty($contact['cid']) || ($contact['contact-type'] != Contact::TYPE_COMMUNITY)) { $body = $item['body'];
continue; $inform = $item['inform'];
} $postopts = $item['postopts'];
$private = $item['private'];
if (!empty($contact['prv']) || ($tag[1] == Tag::TAG_CHARACTER[Tag::EXCLUSIVE_MENTION])) { $str_contact_allow = $item['allow_cid'];
$private_forum = $contact['prv']; $str_group_allow = $item['allow_gid'];
$only_to_forum = ($tag[1] == Tag::TAG_CHARACTER[Tag::EXCLUSIVE_MENTION]); $str_contact_deny = $item['deny_cid'];
$private_id = $contact['id']; $str_group_deny = $item['deny_gid'];
$forum_contact = $contact; } else {
Logger::info('Private forum or exclusive mention', ['url' => $tag[2], 'mention' => $tag[1]]); $inform = '';
} elseif ($str_contact_allow == '<' . $contact['id'] . '>') {
$private_forum = false;
$only_to_forum = true;
$private_id = $contact['id'];
$forum_contact = $contact;
Logger::info('Public forum', ['url' => $tag[2], 'mention' => $tag[1]]);
} else {
Logger::info('Post with forum mention will not be converted to a forum post', ['url' => $tag[2], 'mention' => $tag[1]]);
}
}
Logger::info('Got inform', ['inform' => $inform]);
}
$original_contact_id = $contact_id;
if (!$toplevel_item_id && !empty($forum_contact) && ($private_forum || $only_to_forum)) {
// we tagged a forum in a top level post. Now we change the post
$private = $private_forum ? Item::PRIVATE : Item::UNLISTED;
if ($only_to_forum) {
$postopts = '';
}
if (!$private_forum) {
$str_contact_allow = '';
$str_group_allow = '';
$str_contact_deny = '';
$str_group_deny = '';
}
if ($private_forum || !APContact::getByURL($forum_contact['url'])) {
$str_group_allow = '';
$str_contact_deny = '';
$str_group_deny = '';
if ($private_forum) {
$str_contact_allow = '<' . $private_id . '>';
} else {
$str_contact_allow = '';
}
$contact_id = $private_id;
$contact_record = $forum_contact;
$_REQUEST['origin'] = false;
$wall = 0;
}
} }
/* /*
@ -472,7 +425,7 @@ function item_post(App $a) {
$match = null; $match = null;
if (!$preview && Photo::setPermissionFromBody($body, $uid, $original_contact_id, $str_contact_allow, $str_group_allow, $str_contact_deny, $str_group_deny)) { if (!$preview && Photo::setPermissionFromBody($body, $uid, $contact_id, $str_contact_allow, $str_group_allow, $str_contact_deny, $str_group_deny)) {
$objecttype = Activity\ObjectType::IMAGE; $objecttype = Activity\ObjectType::IMAGE;
} }
@ -487,7 +440,7 @@ function item_post(App $a) {
if (count($attaches)) { if (count($attaches)) {
foreach ($attaches as $attach) { foreach ($attaches as $attach) {
// Ensure to only modify attachments that you own // Ensure to only modify attachments that you own
$srch = '<' . intval($original_contact_id) . '>'; $srch = '<' . intval($contact_id) . '>';
$condition = ['allow_cid' => $srch, 'allow_gid' => '', 'deny_cid' => '', 'deny_gid' => '', $condition = ['allow_cid' => $srch, 'allow_gid' => '', 'deny_cid' => '', 'deny_gid' => '',
'id' => $attach]; 'id' => $attach];
@ -809,12 +762,6 @@ function item_post(App $a) {
} }
} }
// When we are doing some forum posting via ! we have to start the notifier manually.
// These kind of posts don't initiate the notifier call in the item class.
if ($only_to_forum) {
Worker::add(['priority' => PRIORITY_HIGH, 'dont_fork' => false], "Notifier", Delivery::POST, (int)$datarray['uri-id'], (int)$datarray['uid']);
}
Logger::info('post_complete'); Logger::info('post_complete');
if ($api_source) { if ($api_source) {

View File

@ -1,531 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use Friendica\App;
use Friendica\Content\ForumManager;
use Friendica\Content\Text\BBCode;
use Friendica\Core\Cache\Enum\Duration;
use Friendica\Core\Hook;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Group;
use Friendica\Model\Notification;
use Friendica\Model\Post;
use Friendica\Model\Verb;
use Friendica\Protocol\Activity;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Proxy;
use Friendica\Util\Temporal;
use Friendica\Util\XML;
/**
* Outputs the counts and the lists of various notifications
*
* The output format can be controlled via the GET parameter 'format'. It can be
* - xml (deprecated legacy default)
* - json (outputs JSONP with the 'callback' GET parameter)
*
* Expected JSON structure:
* {
* "result": {
* "intro": 0,
* "mail": 0,
* "net": 0,
* "home": 0,
* "register": 0,
* "all-events": 0,
* "all-events-today": 0,
* "events": 0,
* "events-today": 0,
* "birthdays": 0,
* "birthdays-today": 0,
* "groups": [ ],
* "forums": [ ],
* "notification": 0,
* "notifications": [ ],
* "sysmsgs": {
* "notice": [ ],
* "info": [ ]
* }
* }
* }
*
* @param App $a The Friendica App instance
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
function ping_init(App $a)
{
$format = 'xml';
if (isset($_GET['format']) && $_GET['format'] == 'json') {
$format = 'json';
}
$regs = [];
$notifications = [];
$intro_count = 0;
$mail_count = 0;
$home_count = 0;
$network_count = 0;
$register_count = 0;
$sysnotify_count = 0;
$groups_unseen = [];
$forums_unseen = [];
$all_events = 0;
$all_events_today = 0;
$events = 0;
$events_today = 0;
$birthdays = 0;
$birthdays_today = 0;
$data = [];
$data['intro'] = $intro_count;
$data['mail'] = $mail_count;
$data['net'] = $network_count;
$data['home'] = $home_count;
$data['register'] = $register_count;
$data['all-events'] = $all_events;
$data['all-events-today'] = $all_events_today;
$data['events'] = $events;
$data['events-today'] = $events_today;
$data['birthdays'] = $birthdays;
$data['birthdays-today'] = $birthdays_today;
if (local_user()) {
// Different login session than the page that is calling us.
if (!empty($_GET['uid']) && intval($_GET['uid']) != local_user()) {
$data = ['result' => ['invalid' => 1]];
if ($format == 'json') {
if (isset($_GET['callback'])) {
// JSONP support
header("Content-type: application/javascript");
echo $_GET['callback'] . '(' . json_encode($data) . ')';
} else {
header("Content-type: application/json");
echo json_encode($data);
}
} else {
header("Content-type: text/xml");
echo XML::fromArray($data, $xml);
}
exit();
}
$notifications = ping_get_notifications(local_user());
$condition = ["`unseen` AND `uid` = ? AND NOT `origin` AND (`vid` != ? OR `vid` IS NULL)",
local_user(), Verb::getID(Activity::FOLLOW)];
$items = Post::selectForUser(local_user(), ['wall', 'uid', 'uri-id'], $condition, ['limit' => 1000]);
if (DBA::isResult($items)) {
$items_unseen = Post::toArray($items, false);
$arr = ['items' => $items_unseen];
Hook::callAll('network_ping', $arr);
foreach ($items_unseen as $item) {
if ($item['wall']) {
$home_count++;
} else {
$network_count++;
}
}
}
DBA::close($items);
if ($network_count) {
// Find out how unseen network posts are spread across groups
$group_counts = Group::countUnseen();
if (DBA::isResult($group_counts)) {
foreach ($group_counts as $group_count) {
if ($group_count['count'] > 0) {
$groups_unseen[] = $group_count;
}
}
}
$forum_counts = ForumManager::countUnseenItems();
if (DBA::isResult($forum_counts)) {
foreach ($forum_counts as $forum_count) {
if ($forum_count['count'] > 0) {
$forums_unseen[] = $forum_count;
}
}
}
}
$intros1 = DBA::toArray(DBA::p(
"SELECT `intro`.`id`, `intro`.`datetime`,
`contact`.`name`, `contact`.`url`, `contact`.`photo`
FROM `intro` INNER JOIN `contact` ON `intro`.`suggest-cid` = `contact`.`id`
WHERE `intro`.`uid` = ? AND NOT `intro`.`blocked` AND NOT `intro`.`ignore` AND `intro`.`suggest-cid` != 0",
local_user()
));
$intros2 = DBA::toArray(DBA::p(
"SELECT `intro`.`id`, `intro`.`datetime`,
`contact`.`name`, `contact`.`url`, `contact`.`photo`
FROM `intro` INNER JOIN `contact` ON `intro`.`contact-id` = `contact`.`id`
WHERE `intro`.`uid` = ? AND NOT `intro`.`blocked` AND NOT `intro`.`ignore` AND `intro`.`contact-id` != 0 AND (`intro`.`suggest-cid` = 0 OR `intro`.`suggest-cid` IS NULL)",
local_user()
));
$intro_count = count($intros1) + count($intros2);
$intros = $intros1 + $intros2;
$myurl = DI::baseUrl() . '/profile/' . $a->getLoggedInUserNickname();
$mail_count = DBA::count('mail', ["`uid` = ? AND NOT `seen` AND `from-url` != ?", local_user(), $myurl]);
if (intval(DI::config()->get('config', 'register_policy')) === \Friendica\Module\Register::APPROVE && $a->isSiteAdmin()) {
$regs = Friendica\Model\Register::getPending();
if (DBA::isResult($regs)) {
$register_count = count($regs);
}
}
$cachekey = "ping_init:".local_user();
$ev = DI::cache()->get($cachekey);
if (is_null($ev)) {
$ev = DBA::selectToArray('event', ['type', 'start'],
["`uid` = ? AND `start` < ? AND `finish` > ? AND NOT `ignore`",
local_user(), DateTimeFormat::utc('now + 7 days'), DateTimeFormat::utcNow()]);
if (DBA::isResult($ev)) {
DI::cache()->set($cachekey, $ev, Duration::HOUR);
}
}
if (DBA::isResult($ev)) {
$all_events = count($ev);
if ($all_events) {
$str_now = DateTimeFormat::localNow('Y-m-d');
foreach ($ev as $x) {
$bd = false;
if ($x['type'] === 'birthday') {
$birthdays ++;
$bd = true;
} else {
$events ++;
}
if (DateTimeFormat::local($x['start'], 'Y-m-d') === $str_now) {
$all_events_today ++;
if ($bd) {
$birthdays_today ++;
} else {
$events_today ++;
}
}
}
}
}
$data['intro'] = $intro_count;
$data['mail'] = $mail_count;
$data['net'] = ($network_count < 1000) ? $network_count : '999+';
$data['home'] = ($home_count < 1000) ? $home_count : '999+';
$data['register'] = $register_count;
$data['all-events'] = $all_events;
$data['all-events-today'] = $all_events_today;
$data['events'] = $events;
$data['events-today'] = $events_today;
$data['birthdays'] = $birthdays;
$data['birthdays-today'] = $birthdays_today;
if (DBA::isResult($notifications)) {
foreach ($notifications as $notif) {
if ($notif['seen'] == 0) {
$sysnotify_count ++;
}
}
}
// merge all notification types in one array
if (DBA::isResult($intros)) {
foreach ($intros as $intro) {
$notif = [
'id' => 0,
'href' => DI::baseUrl() . '/notifications/intros/' . $intro['id'],
'name' => BBCode::convert($intro['name']),
'url' => $intro['url'],
'photo' => $intro['photo'],
'date' => $intro['datetime'],
'seen' => false,
'message' => DI::l10n()->t('{0} wants to be your friend'),
];
$notifications[] = $notif;
}
}
if (DBA::isResult($regs)) {
if (count($regs) <= 1 || DI::pConfig()->get(local_user(), 'system', 'detailed_notif')) {
foreach ($regs as $reg) {
$notif = [
'id' => 0,
'href' => DI::baseUrl() . '/admin/users/pending',
'name' => $reg['name'],
'url' => $reg['url'],
'photo' => $reg['micro'],
'date' => $reg['created'],
'seen' => false,
'message' => DI::l10n()->t('{0} requested registration'),
];
$notifications[] = $notif;
}
} else {
$notif = [
'id' => 0,
'href' => DI::baseUrl() . '/admin/users/pending',
'name' => $regs[0]['name'],
'url' => $regs[0]['url'],
'photo' => $regs[0]['micro'],
'date' => $regs[0]['created'],
'seen' => false,
'message' => DI::l10n()->t('{0} and %d others requested registration', count($regs) - 1),
];
$notifications[] = $notif;
}
}
// sort notifications by $[]['date']
$sort_function = function ($a, $b) {
$adate = strtotime($a['date']);
$bdate = strtotime($b['date']);
// Unseen messages are kept at the top
// The value 31536000 means one year. This should be enough :-)
if (!$a['seen']) {
$adate += 31536000;
}
if (!$b['seen']) {
$bdate += 31536000;
}
if ($adate == $bdate) {
return 0;
}
return ($adate < $bdate) ? 1 : -1;
};
usort($notifications, $sort_function);
array_walk($notifications, function (&$notification) {
$notification['photo'] = Contact::getAvatarUrlForUrl($notification['url'], local_user(), Proxy::SIZE_MICRO);
$notification['timestamp'] = DateTimeFormat::local($notification['date']);
$notification['date'] = Temporal::getRelativeDate($notification['date']);
});
}
$sysmsgs = [];
$sysmsgs_info = [];
if (!empty($_SESSION['sysmsg'])) {
$sysmsgs = $_SESSION['sysmsg'];
unset($_SESSION['sysmsg']);
}
if (!empty($_SESSION['sysmsg_info'])) {
$sysmsgs_info = $_SESSION['sysmsg_info'];
unset($_SESSION['sysmsg_info']);
}
if ($format == 'json') {
$notification_count = $sysnotify_count + $intro_count + $register_count;
$data['groups'] = $groups_unseen;
$data['forums'] = $forums_unseen;
$data['notification'] = ($notification_count < 50) ? $notification_count : '49+';
$data['notifications'] = $notifications;
$data['sysmsgs'] = [
'notice' => $sysmsgs,
'info' => $sysmsgs_info
];
$json_payload = json_encode(["result" => $data]);
if (isset($_GET['callback'])) {
// JSONP support
header("Content-type: application/javascript");
echo $_GET['callback'] . '(' . $json_payload . ')';
} else {
header("Content-type: application/json");
echo $json_payload;
}
} else {
// Legacy slower XML format output
$data = ping_format_xml_data($data, $sysnotify_count, $notifications, $sysmsgs, $sysmsgs_info, $groups_unseen, $forums_unseen);
header("Content-type: text/xml");
echo XML::fromArray(["result" => $data], $xml);
}
exit();
}
/**
* Retrieves the notifications array for the given user ID
*
* @param int $uid User id
* @return array Associative array of notifications
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
function ping_get_notifications($uid)
{
$result = [];
$offset = 0;
$seen = false;
$seensql = "NOT";
$order = "DESC";
$quit = false;
do {
$r = DBA::toArray(DBA::p(
"SELECT `notify`.*, `post`.`visible`, `post`.`deleted`
FROM `notify` LEFT JOIN `post` ON `post`.`uri-id` = `notify`.`uri-id`
WHERE `notify`.`uid` = ? AND `notify`.`msg` != ''
AND NOT (`notify`.`type` IN (?, ?))
AND $seensql `notify`.`seen` ORDER BY `notify`.`date` $order LIMIT ?, 50",
$uid,
Notification\Type::INTRO,
Notification\Type::MAIL,
$offset
));
if (!$r && !$seen) {
$seen = true;
$seensql = "";
$order = "DESC";
$offset = 0;
} elseif (!$r) {
$quit = true;
} else {
$offset += 50;
}
foreach ($r as $notification) {
if (is_null($notification["visible"])) {
$notification["visible"] = true;
}
if (is_null($notification["deleted"])) {
$notification["deleted"] = 0;
}
if ($notification["msg_cache"]) {
$notification["name"] = $notification["name_cache"];
$notification["message"] = $notification["msg_cache"];
} else {
$notification["name"] = strip_tags(BBCode::convert($notification["name"]));
$notification["message"] = \Friendica\Navigation\Notifications\Entity\Notify::formatMessage($notification["name"], BBCode::toPlaintext($notification["msg"]));
// @todo Replace this with a call of the Notify model class
DBA::update('notify', ['name_cache' => $notification["name"], 'msg_cache' => $notification["message"]], ['id' => $notification["id"]]);
}
$notification["href"] = DI::baseUrl() . "/notification/" . $notification["id"];
if ($notification["visible"]
&& !$notification["deleted"]
&& empty($result['p:' . $notification['parent']])
) {
// Should we condense the notifications or show them all?
if (($notification['verb'] != Activity::POST) || DI::pConfig()->get(local_user(), 'system', 'detailed_notif')) {
$result[] = $notification;
} else {
$result['p:' . $notification['parent']] = $notification;
}
}
}
} while ((count($result) < 50) && !$quit);
return($result);
}
/**
* Backward-compatible XML formatting for ping.php output
* @deprecated
*
* @param array $data The initial ping data array
* @param int $sysnotify_count Number of unseen system notifications
* @param array $notifs Complete list of notification
* @param array $sysmsgs List of system notice messages
* @param array $sysmsgs_info List of system info messages
* @param array $groups_unseen List of unseen group items
* @param array $forums_unseen List of unseen forum items
*
* @return array XML-transform ready data array
*/
function ping_format_xml_data($data, $sysnotify_count, $notifs, $sysmsgs, $sysmsgs_info, $groups_unseen, $forums_unseen)
{
$notifications = [];
foreach ($notifs as $key => $notif) {
$notifications[$key . ':note'] = $notif['message'];
$notifications[$key . ':@attributes'] = [
'id' => $notif['id'],
'href' => $notif['href'],
'name' => $notif['name'],
'url' => $notif['url'],
'photo' => $notif['photo'],
'date' => $notif['date'],
'seen' => $notif['seen'],
'timestamp' => $notif['timestamp']
];
}
$sysmsg = [];
foreach ($sysmsgs as $key => $m) {
$sysmsg[$key . ':notice'] = $m;
}
foreach ($sysmsgs_info as $key => $m) {
$sysmsg[$key . ':info'] = $m;
}
$data['notif'] = $notifications;
$data['@attributes'] = ['count' => $sysnotify_count + $data['intro'] + $data['mail'] + $data['register']];
$data['sysmsgs'] = $sysmsg;
if ($data['register'] == 0) {
unset($data['register']);
}
$groups = [];
if (count($groups_unseen)) {
foreach ($groups_unseen as $key => $item) {
$groups[$key . ':group'] = $item['count'];
$groups[$key . ':@attributes'] = ['id' => $item['id']];
}
$data['groups'] = $groups;
}
$forums = [];
if (count($forums_unseen)) {
foreach ($forums_unseen as $key => $item) {
$forums[$key . ':forum'] = $item['count'];
$forums[$key . ':@attributes'] = ['id' => $item['id']];
}
$data['forums'] = $forums;
}
return $data;
}

View File

@ -27,6 +27,7 @@ use Friendica\Core\Protocol;
use Friendica\Core\Renderer; use Friendica\Core\Renderer;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\DI; use Friendica\DI;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Strings; use Friendica\Util\Strings;
use Friendica\Util\XML; use Friendica\Util\XML;
@ -121,10 +122,12 @@ function poco_init(App $a) {
if (isset($contact['account-type'])) { if (isset($contact['account-type'])) {
$contact['contact-type'] = $contact['account-type']; $contact['contact-type'] = $contact['account-type'];
} }
$about = DI::cache()->get("about:" . $contact['updated'] . ":" . $contact['nurl']);
$cacheKey = 'about:' . $contact['nick'] . ':' . DateTimeFormat::utc($contact['updated'], DateTimeFormat::ATOM);
$about = DI::cache()->get($cacheKey);
if (is_null($about)) { if (is_null($about)) {
$about = BBCode::convertForUriId($contact['uri-id'], $contact['about']); $about = BBCode::convertForUriId($contact['uri-id'], $contact['about']);
DI::cache()->set("about:" . $contact['updated'] . ":" . $contact['nurl'], $about); DI::cache()->set($cacheKey, $about);
} }
// Non connected persons can only see the keywords of a Diaspora account // Non connected persons can only see the keywords of a Diaspora account

View File

@ -31,11 +31,14 @@ use Friendica\Core\Worker;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\Group; use Friendica\Model\Group;
use Friendica\Model\Item;
use Friendica\Model\Notification; use Friendica\Model\Notification;
use Friendica\Model\Profile; use Friendica\Model\Profile;
use Friendica\Model\User; use Friendica\Model\User;
use Friendica\Model\Verb;
use Friendica\Module\BaseSettings; use Friendica\Module\BaseSettings;
use Friendica\Module\Security\Login; use Friendica\Module\Security\Login;
use Friendica\Protocol\Activity;
use Friendica\Protocol\Email; use Friendica\Protocol\Email;
use Friendica\Util\Temporal; use Friendica\Util\Temporal;
use Friendica\Worker\Delivery; use Friendica\Worker\Delivery;
@ -107,7 +110,7 @@ function settings_post(App $a)
'port' => $mail_port, 'port' => $mail_port,
'ssltype' => $mail_ssl, 'ssltype' => $mail_ssl,
'user' => $mail_user, 'user' => $mail_user,
`action` => $mail_action, 'action' => $mail_action,
'movetofolder' => $mail_movetofolder, 'movetofolder' => $mail_movetofolder,
'mailbox' => 'INBOX', 'mailbox' => 'INBOX',
'reply_to' => $mail_replyto, 'reply_to' => $mail_replyto,
@ -239,7 +242,6 @@ function settings_post(App $a)
$allow_location = ((!empty($_POST['allow_location']) && (intval($_POST['allow_location']) == 1)) ? 1: 0); $allow_location = ((!empty($_POST['allow_location']) && (intval($_POST['allow_location']) == 1)) ? 1: 0);
$publish = ((!empty($_POST['profile_in_directory']) && (intval($_POST['profile_in_directory']) == 1)) ? 1: 0); $publish = ((!empty($_POST['profile_in_directory']) && (intval($_POST['profile_in_directory']) == 1)) ? 1: 0);
$net_publish = ((!empty($_POST['profile_in_netdirectory']) && (intval($_POST['profile_in_netdirectory']) == 1)) ? 1: 0); $net_publish = ((!empty($_POST['profile_in_netdirectory']) && (intval($_POST['profile_in_netdirectory']) == 1)) ? 1: 0);
$old_visibility = ((!empty($_POST['visibility']) && (intval($_POST['visibility']) == 1)) ? 1 : 0);
$account_type = ((!empty($_POST['account-type']) && (intval($_POST['account-type']))) ? intval($_POST['account-type']) : 0); $account_type = ((!empty($_POST['account-type']) && (intval($_POST['account-type']))) ? intval($_POST['account-type']) : 0);
$page_flags = ((!empty($_POST['page-flags']) && (intval($_POST['page-flags']))) ? intval($_POST['page-flags']) : 0); $page_flags = ((!empty($_POST['page-flags']) && (intval($_POST['page-flags']))) ? intval($_POST['page-flags']) : 0);
$blockwall = ((!empty($_POST['blockwall']) && (intval($_POST['blockwall']) == 1)) ? 0: 1); // this setting is inverted! $blockwall = ((!empty($_POST['blockwall']) && (intval($_POST['blockwall']) == 1)) ? 0: 1); // this setting is inverted!
@ -352,7 +354,18 @@ function settings_post(App $a)
DI::pConfig()->set(local_user(), 'expire', 'photos', $expire_photos); DI::pConfig()->set(local_user(), 'expire', 'photos', $expire_photos);
DI::pConfig()->set(local_user(), 'expire', 'network_only', $expire_network_only); DI::pConfig()->set(local_user(), 'expire', 'network_only', $expire_network_only);
// Reset like notifications when they are going to be shown again
if (!DI::pConfig()->get(local_user(), 'system', 'notify_like') && $notify_like) {
DI::notification()->setAllSeenForUser(local_user(), ['vid' => Verb::getID(Activity::LIKE)]);
}
DI::pConfig()->set(local_user(), 'system', 'notify_like', $notify_like); DI::pConfig()->set(local_user(), 'system', 'notify_like', $notify_like);
// Reset share notifications when they are going to be shown again
if (!DI::pConfig()->get(local_user(), 'system', 'notify_announce') && $notify_announce) {
DI::notification()->setAllSeenForUser(local_user(), ['vid' => Verb::getID(Activity::ANNOUNCE)]);
}
DI::pConfig()->set(local_user(), 'system', 'notify_announce', $notify_announce); DI::pConfig()->set(local_user(), 'system', 'notify_announce', $notify_announce);
DI::pConfig()->set(local_user(), 'system', 'email_textonly', $email_textonly); DI::pConfig()->set(local_user(), 'system', 'email_textonly', $email_textonly);
@ -361,16 +374,21 @@ function settings_post(App $a)
DI::pConfig()->set(local_user(), 'system', 'unlisted', $unlisted); DI::pConfig()->set(local_user(), 'system', 'unlisted', $unlisted);
DI::pConfig()->set(local_user(), 'system', 'accessible-photos', $accessiblephotos); DI::pConfig()->set(local_user(), 'system', 'accessible-photos', $accessiblephotos);
if ($account_type == User::ACCOUNT_TYPE_COMMUNITY) {
$str_group_allow = '';
$str_contact_allow = '';
$str_group_deny = '';
$str_contact_deny = '';
DI::pConfig()->set(local_user(), 'system', 'unlisted', true);
$blockwall = true;
$blocktags = true;
$hide_friends = true;
}
if ($page_flags == User::PAGE_FLAGS_PRVGROUP) { if ($page_flags == User::PAGE_FLAGS_PRVGROUP) {
$hidewall = 1; $str_group_allow = '<' . Group::FOLLOWERS . '>';
if (!$str_contact_allow && !$str_group_allow && !$str_contact_deny && !$str_group_deny) {
if ($def_gid) {
info(DI::l10n()->t('Private forum has no privacy permissions. Using default privacy group.'));
$str_group_allow = '<' . $def_gid . '>';
} else {
notice(DI::l10n()->t('Private forum has no privacy permissions and no default privacy group.'));
}
}
} }
$fields = ['username' => $username, 'email' => $email, 'timezone' => $timezone, $fields = ['username' => $username, 'email' => $email, 'timezone' => $timezone,
@ -570,7 +588,17 @@ function settings_content(App $a)
'$ostat_enabled' => $ostat_enabled, '$ostat_enabled' => $ostat_enabled,
'$general_settings' => DI::l10n()->t('General Social Media Settings'), '$general_settings' => DI::l10n()->t('General Social Media Settings'),
'$accept_only_sharer' => ['accept_only_sharer', DI::l10n()->t('Accept only top level posts by contacts you follow'), $accept_only_sharer, DI::l10n()->t('The system does an auto completion of threads when a comment arrives. This has got the side effect that you can receive posts that had been started by a non-follower but had been commented by someone you follow. This setting deactivates this behaviour. When activated, you strictly only will receive posts from people you really do follow.')], '$accept_only_sharer' => [
'accept_only_sharer',
DI::l10n()->t('Followed content scope'),
$accept_only_sharer,
DI::l10n()->t('By default, conversations in which your follows participated but didn\'t start will be shown in your timeline. You can turn this behavior off, or expand it to the conversations in which your follows liked a post.'),
[
Item::COMPLETION_NONE => DI::l10n()->t('Only conversations my follows started'),
Item::COMPLETION_COMMENT => DI::l10n()->t('Conversations my follows started or commented on (default)'),
Item::COMPLETION_LIKE => DI::l10n()->t('Any conversation my follows interacted with, including likes'),
]
],
'$enable_cw' => ['enable_cw', DI::l10n()->t('Enable Content Warning'), $enable_cw, DI::l10n()->t('Users on networks like Mastodon or Pleroma are able to set a content warning field which collapse their post by default. This enables the automatic collapsing instead of setting the content warning as the post title. Doesn\'t affect any other content filtering you eventually set up.')], '$enable_cw' => ['enable_cw', DI::l10n()->t('Enable Content Warning'), $enable_cw, DI::l10n()->t('Users on networks like Mastodon or Pleroma are able to set a content warning field which collapse their post by default. This enables the automatic collapsing instead of setting the content warning as the post title. Doesn\'t affect any other content filtering you eventually set up.')],
'$enable_smart_shortening' => ['enable_smart_shortening', DI::l10n()->t('Enable intelligent shortening'), $enable_smart_shortening, DI::l10n()->t('Normally the system tries to find the best link to add to shortened posts. If disabled, every shortened post will always point to the original friendica post.')], '$enable_smart_shortening' => ['enable_smart_shortening', DI::l10n()->t('Enable intelligent shortening'), $enable_smart_shortening, DI::l10n()->t('Normally the system tries to find the best link to add to shortened posts. If disabled, every shortened post will always point to the original friendica post.')],
'$simple_shortening' => ['simple_shortening', DI::l10n()->t('Enable simple text shortening'), $simple_shortening, DI::l10n()->t('Normally the system shortens posts at the next line feed. If this option is enabled then the system will shorten the text at the maximum character limit.')], '$simple_shortening' => ['simple_shortening', DI::l10n()->t('Enable simple text shortening'), $simple_shortening, DI::l10n()->t('Normally the system shortens posts at the next line feed. If this option is enabled then the system will shorten the text at the maximum character limit.')],
@ -756,7 +784,7 @@ function settings_content(App $a)
'$allowloc' => ['allow_location', DI::l10n()->t('Use Browser Location:'), ($user['allow_location'] == 1), ''], '$allowloc' => ['allow_location', DI::l10n()->t('Use Browser Location:'), ($user['allow_location'] == 1), ''],
'$h_prv' => DI::l10n()->t('Security and Privacy Settings'), '$h_prv' => DI::l10n()->t('Security and Privacy Settings'),
'$visibility' => $profile['net-publish'], '$is_community' => ($user['account-type'] == User::ACCOUNT_TYPE_COMMUNITY),
'$maxreq' => ['maxreq', DI::l10n()->t('Maximum Friend Requests/Day:'), $maxreq , DI::l10n()->t("\x28to prevent spam abuse\x29")], '$maxreq' => ['maxreq', DI::l10n()->t('Maximum Friend Requests/Day:'), $maxreq , DI::l10n()->t("\x28to prevent spam abuse\x29")],
'$profile_in_dir' => $profile_in_dir, '$profile_in_dir' => $profile_in_dir,
'$profile_in_net_dir' => ['profile_in_netdirectory', DI::l10n()->t('Allow your profile to be searchable globally?'), $profile['net-publish'], DI::l10n()->t("Activate this setting if you want others to easily find and follow you. Your profile will be searchable on remote systems. This setting also determines whether Friendica will inform search engines that your profile should be indexed or not.") . $net_pub_desc], '$profile_in_net_dir' => ['profile_in_netdirectory', DI::l10n()->t('Allow your profile to be searchable globally?'), $profile['net-publish'], DI::l10n()->t("Activate this setting if you want others to easily find and follow you. Your profile will be searchable on remote systems. This setting also determines whether Friendica will inform search engines that your profile should be indexed or not.") . $net_pub_desc],

View File

@ -122,8 +122,7 @@ function unfollow_process(string $url)
$owner = User::getOwnerDataById($uid); $owner = User::getOwnerDataById($uid);
if (!$owner) { if (!$owner) {
(new \Friendica\Module\Security\Logout())->init(); throw new \Friendica\Network\HTTPException\NotFoundException();
// NOTREACHED
} }
$condition = ["`uid` = ? AND (`rel` = ? OR `rel` = ?) AND (`nurl` = ? OR `alias` = ? OR `alias` = ?)", $condition = ["`uid` = ? AND (`rel` = ? OR `rel` = ?) AND (`nurl` = ? OR `alias` = ? OR `alias` = ?)",
@ -140,15 +139,10 @@ function unfollow_process(string $url)
$return_path = $base_return_path . '/' . $contact['id']; $return_path = $base_return_path . '/' . $contact['id'];
try { try {
$result = Contact::terminateFriendship($owner, $contact); Contact::unfollow($contact);
$notice_message = DI::l10n()->t('Contact was successfully unfollowed');
if ($result === false) {
$notice_message = DI::l10n()->t('Unable to unfollow this contact, please retry in a few minutes or contact your administrator.');
} else {
$notice_message = DI::l10n()->t('Contact was successfully unfollowed');
}
} catch (Exception $e) { } catch (Exception $e) {
DI::logger()->error($e->getMessage(), ['owner' => $owner, 'contact' => $contact]); DI::logger()->error($e->getMessage(), ['contact' => $contact]);
$notice_message = DI::l10n()->t('Unable to unfollow this contact, please contact your administrator'); $notice_message = DI::l10n()->t('Unable to unfollow this contact, please contact your administrator');
} }

View File

@ -64,6 +64,7 @@ Options
-s|--savedb Save the DB credentials to the file (if environment variables is used) -s|--savedb Save the DB credentials to the file (if environment variables is used)
-H|--dbhost <host> The host of the mysql/mariadb database (env MYSQL_HOST) -H|--dbhost <host> The host of the mysql/mariadb database (env MYSQL_HOST)
-p|--dbport <port> The port of the mysql/mariadb database (env MYSQL_PORT) -p|--dbport <port> The port of the mysql/mariadb database (env MYSQL_PORT)
-s|--dbsocket <socket> The socket of the mysql/mariadb database (env MYSQL_SOCKET)
-d|--dbdata <database> The name of the mysql/mariadb database (env MYSQL_DATABASE) -d|--dbdata <database> The name of the mysql/mariadb database (env MYSQL_DATABASE)
-u|--dbuser <username> The username of the mysql/mariadb database login (env MYSQL_USER or MYSQL_USERNAME) -u|--dbuser <username> The username of the mysql/mariadb database login (env MYSQL_USER or MYSQL_USERNAME)
-P|--dbpass <password> The password of the mysql/mariadb database login (env MYSQL_PASSWORD) -P|--dbpass <password> The password of the mysql/mariadb database login (env MYSQL_PASSWORD)
@ -76,6 +77,7 @@ Options
Environment variables Environment variables
MYSQL_HOST The host of the mysql/mariadb database (mandatory if mysql and environment is used) MYSQL_HOST The host of the mysql/mariadb database (mandatory if mysql and environment is used)
MYSQL_PORT The port of the mysql/mariadb database MYSQL_PORT The port of the mysql/mariadb database
MYSQL_SOCKET The socket of the mysql/mariadb database
MYSQL_USERNAME|MYSQL_USER The username of the mysql/mariadb database login (MYSQL_USERNAME is for mysql, MYSQL_USER for mariadb) MYSQL_USERNAME|MYSQL_USER The username of the mysql/mariadb database login (MYSQL_USERNAME is for mysql, MYSQL_USER for mariadb)
MYSQL_PASSWORD The password of the mysql/mariadb database login MYSQL_PASSWORD The password of the mysql/mariadb database login
MYSQL_DATABASE The name of the mysql/mariadb database MYSQL_DATABASE The name of the mysql/mariadb database
@ -157,6 +159,7 @@ HELP;
$db_host = $this->getOption(['H', 'dbhost'], ($save_db) ? (getenv('MYSQL_HOST')) : Installer::DEFAULT_HOST); $db_host = $this->getOption(['H', 'dbhost'], ($save_db) ? (getenv('MYSQL_HOST')) : Installer::DEFAULT_HOST);
$db_port = $this->getOption(['p', 'dbport'], ($save_db) ? getenv('MYSQL_PORT') : null); $db_port = $this->getOption(['p', 'dbport'], ($save_db) ? getenv('MYSQL_PORT') : null);
$db_socket = $this->getOption(['s', 'dbsocket'], ($save_db) ? getenv('MYSQL_SOCKET') : null);
$configCache->set('database', 'hostname', $db_host . (!empty($db_port) ? ':' . $db_port : '')); $configCache->set('database', 'hostname', $db_host . (!empty($db_port) ? ':' . $db_port : ''));
$configCache->set('database', 'database', $configCache->set('database', 'database',
$this->getOption(['d', 'dbdata'], $this->getOption(['d', 'dbdata'],

View File

@ -151,7 +151,7 @@ HELP;
$this->out("{$cat}.{$key}[{$k}] => " . (is_array($v) ? implode(', ', $v) : $v)); $this->out("{$cat}.{$key}[{$k}] => " . (is_array($v) ? implode(', ', $v) : $v));
} }
} else { } else {
$this->out("{$cat}.{$key} => " . $value); $this->out("{$cat}.{$key} => " . ($value ?? 'NULL'));
} }
} }

View File

@ -199,19 +199,18 @@ HELP;
throw new RuntimeException('Contact not found'); throw new RuntimeException('Contact not found');
} }
$user = UserModel::getById($contact['uid']); if (empty($contact['uid'])) {
throw new RuntimeException('Contact must be user-specific (uid != 0)');
}
try { try {
$result = ContactModel::terminateFriendship($user, $contact); ContactModel::unfollow($contact);
if ($result === false) {
throw new RuntimeException('Unable to unfollow this contact, please retry in a few minutes or check the logs.');
}
$this->out('Contact was successfully unfollowed'); $this->out('Contact was successfully unfollowed');
return true; return true;
} catch (\Exception $e) { } catch (\Exception $e) {
DI::logger()->error($e->getMessage(), ['owner' => $user, 'contact' => $contact]); DI::logger()->error($e->getMessage(), ['contact' => $contact]);
throw new RuntimeException('Unable to unfollow this contact, please check the log'); throw new RuntimeException('Unable to unfollow this contact, please check the log');
} }
} }

View File

@ -154,7 +154,7 @@ class Conversation
// Skip when the causer of the parent is the same than the author of the announce // Skip when the causer of the parent is the same than the author of the announce
if (($verb == Activity::ANNOUNCE) && Post::exists(['uri-id' => $activity['thr-parent-id'], if (($verb == Activity::ANNOUNCE) && Post::exists(['uri-id' => $activity['thr-parent-id'],
'uid' => $activity['uid'], 'causer-id' => $activity['author-id'], 'gravity' => GRAVITY_PARENT])) { 'uid' => $activity['uid'], 'causer-id' => $activity['author-id'], 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT]])) {
continue; continue;
} }
@ -843,7 +843,7 @@ class Conversation
$row['owner-name'] = $row['causer-name']; $row['owner-name'] = $row['causer-name'];
} }
if (($row['gravity'] == GRAVITY_PARENT) && !empty($row['causer-id'])) { if (in_array($row['gravity'], [GRAVITY_PARENT, GRAVITY_COMMENT]) && !empty($row['causer-id'])) {
$causer = ['uid' => 0, 'id' => $row['causer-id'], 'network' => $row['causer-network'], 'url' => $row['causer-link']]; $causer = ['uid' => 0, 'id' => $row['causer-id'], 'network' => $row['causer-network'], 'url' => $row['causer-link']];
$row['reshared'] = $this->l10n->t('%s reshared this.', '<a href="'. htmlentities(Contact::magicLinkByContact($causer)) .'">' . htmlentities($row['causer-name']) . '</a>'); $row['reshared'] = $this->l10n->t('%s reshared this.', '<a href="'. htmlentities(Contact::magicLinkByContact($causer)) .'">' . htmlentities($row['causer-name']) . '</a>');

View File

@ -104,6 +104,7 @@ class Feature
DI::l10n()->t('Post Composition Features'), DI::l10n()->t('Post Composition Features'),
['aclautomention', DI::l10n()->t('Auto-mention Forums'), DI::l10n()->t('Add/remove mention when a forum page is selected/deselected in ACL window.'), false, DI::config()->get('feature_lock', 'aclautomention', false)], ['aclautomention', DI::l10n()->t('Auto-mention Forums'), DI::l10n()->t('Add/remove mention when a forum page is selected/deselected in ACL window.'), false, DI::config()->get('feature_lock', 'aclautomention', false)],
['explicit_mentions', DI::l10n()->t('Explicit Mentions'), DI::l10n()->t('Add explicit mentions to comment box for manual control over who gets mentioned in replies.'), false, DI::config()->get('feature_lock', 'explicit_mentions', false)], ['explicit_mentions', DI::l10n()->t('Explicit Mentions'), DI::l10n()->t('Add explicit mentions to comment box for manual control over who gets mentioned in replies.'), false, DI::config()->get('feature_lock', 'explicit_mentions', false)],
['add_abstract', DI::l10n()->t('Add an abstract from ActivityPub content warnings'), DI::l10n()->t('Add an abstract when commenting on ActivityPub posts with a content warning. Abstracts are displayed as content warning on systems like Mastodon or Pleroma.'), false, DI::config()->get('feature_lock', 'add_abstract', false)],
], ],
// Item tools // Item tools

View File

@ -21,12 +21,15 @@
namespace Friendica\Content; namespace Friendica\Content;
use Friendica\Content\Text\BBCode;
use Friendica\Core\Hook; use Friendica\Core\Hook;
use Friendica\Core\L10n; use Friendica\Core\L10n;
use Friendica\Core\Logger;
use Friendica\Core\Protocol; use Friendica\Core\Protocol;
use Friendica\Core\Session; use Friendica\Core\Session;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Friendica\Model\Group;
use Friendica\Model\Item as ModelItem; use Friendica\Model\Item as ModelItem;
use Friendica\Model\Tag; use Friendica\Model\Tag;
use Friendica\Model\Post; use Friendica\Model\Post;
@ -490,4 +493,88 @@ class Item
return true; return true;
} }
public function expandTags(array $item, bool $setPermissions = false)
{
// Look for any tags and linkify them
$item['inform'] = '';
$private_forum = false;
$private_id = null;
$only_to_forum = false;
$forum_contact = [];
$receivers = [];
// Convert mentions in the body to a unified format
$item['body'] = BBCode::setMentions($item['body'], $item['uid'], $item['network']);
// Search for forum mentions
foreach (Tag::getFromBody($item['body'], Tag::TAG_CHARACTER[Tag::MENTION] . Tag::TAG_CHARACTER[Tag::EXCLUSIVE_MENTION]) as $tag) {
$contact = Contact::getByURLForUser($tag[2], $item['uid']);
$receivers[] = $contact['id'];
if (!empty($item['inform'])) {
$item['inform'] .= ',';
}
$item['inform'] .= 'cid:' . $contact['id'];
if (($item['gravity'] == GRAVITY_COMMENT) || empty($contact['cid']) || ($contact['contact-type'] != Contact::TYPE_COMMUNITY)) {
continue;
}
if (!empty($contact['prv']) || ($tag[1] == Tag::TAG_CHARACTER[Tag::EXCLUSIVE_MENTION])) {
$private_forum = $contact['prv'];
$only_to_forum = ($tag[1] == Tag::TAG_CHARACTER[Tag::EXCLUSIVE_MENTION]);
$private_id = $contact['id'];
$forum_contact = $contact;
Logger::info('Private forum or exclusive mention', ['url' => $tag[2], 'mention' => $tag[1]]);
} elseif ($item['allow_cid'] == '<' . $contact['id'] . '>') {
$private_forum = false;
$only_to_forum = true;
$private_id = $contact['id'];
$forum_contact = $contact;
Logger::info('Public forum', ['url' => $tag[2], 'mention' => $tag[1]]);
} else {
Logger::info('Post with forum mention will not be converted to a forum post', ['url' => $tag[2], 'mention' => $tag[1]]);
}
}
Logger::info('Got inform', ['inform' => $item['inform']]);
if (($item['gravity'] == GRAVITY_PARENT) && !empty($forum_contact) && ($private_forum || $only_to_forum)) {
// we tagged a forum in a top level post. Now we change the post
$item['private'] = $private_forum ? ModelItem::PRIVATE : ModelItem::UNLISTED;
if ($only_to_forum) {
$item['postopts'] = '';
}
$item['deny_cid'] = '';
$item['deny_gid'] = '';
if ($private_forum) {
$item['allow_cid'] = '<' . $private_id . '>';
$item['allow_gid'] = '<' . Group::getIdForForum($forum_contact['id']) . '>';
} else {
$item['allow_cid'] = '';
$item['allow_gid'] = '';
}
} elseif ($setPermissions && ($item['gravity'] == GRAVITY_PARENT)) {
if (empty($receivers)) {
// For security reasons direct posts without any receiver will be posts to yourself
$self = Contact::selectFirst(['id'], ['uid' => $item['uid'], 'self' => true]);
$receivers[] = $self['id'];
}
$item['private'] = ModelItem::PRIVATE;
$item['allow_cid'] = '';
$item['allow_gid'] = '';
$item['deny_cid'] = '';
$item['deny_gid'] = '';
foreach ($receivers as $receiver) {
$item['allow_cid'] .= '<' . $receiver . '>';
}
}
return $item;
}
} }

View File

@ -2086,8 +2086,8 @@ class BBCode
public static function stripAbstract($text) public static function stripAbstract($text)
{ {
DI::profiler()->startRecording('rendering'); DI::profiler()->startRecording('rendering');
$text = preg_replace("/[\s|\n]*\[abstract\].*?\[\/abstract\][\s|\n]*/ism", '', $text); $text = preg_replace("/[\s|\n]*\[abstract\].*?\[\/abstract\][\s|\n]*/ism", ' ', $text);
$text = preg_replace("/[\s|\n]*\[abstract=.*?\].*?\[\/abstract][\s|\n]*/ism", '', $text); $text = preg_replace("/[\s|\n]*\[abstract=.*?\].*?\[\/abstract][\s|\n]*/ism", ' ', $text);
DI::profiler()->stopRecording(); DI::profiler()->stopRecording();
return $text; return $text;

View File

@ -318,23 +318,20 @@ class Widget
/** /**
* Return categories widget * Return categories widget
* *
* @param string $baseurl baseurl * @param int $uid Id of the user owning the categories
* @param string $selected optional, default empty * @param string $baseurl Base page URL
* @param string $selected Selected category
* @return string|void * @return string|void
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/ */
public static function categories($baseurl, $selected = '') public static function categories(int $uid, string $baseurl, string $selected = '')
{ {
$a = DI::app();
$uid = intval($a->getProfileOwner());
if (!Feature::isEnabled($uid, 'categories')) { if (!Feature::isEnabled($uid, 'categories')) {
return ''; return '';
} }
$terms = array(); $terms = array();
foreach (Post\Category::getArray(local_user(), Post\Category::CATEGORY) as $savedFolderName) { foreach (Post\Category::getArray($uid, Post\Category::CATEGORY) as $savedFolderName) {
$terms[] = ['ref' => $savedFolderName, 'name' => $savedFolderName]; $terms[] = ['ref' => $savedFolderName, 'name' => $savedFolderName];
} }

View File

@ -99,7 +99,7 @@ class VCard
'$network_link' => $network_link, '$network_link' => $network_link,
'$network_avatar' => $network_avatar, '$network_avatar' => $network_avatar,
'$network' => DI::l10n()->t('Network:'), '$network' => DI::l10n()->t('Network:'),
'$account_type' => Contact::getAccountType($contact), '$account_type' => Contact::getAccountType($contact['contact-type']),
'$follow' => DI::l10n()->t('Follow'), '$follow' => DI::l10n()->t('Follow'),
'$follow_link' => $follow_link, '$follow_link' => $follow_link,
'$unfollow' => DI::l10n()->t('Unfollow'), '$unfollow' => DI::l10n()->t('Unfollow'),

View File

@ -51,7 +51,7 @@ class ACL
* @return string * @return string
* @throws \Exception * @throws \Exception
*/ */
public static function getMessageContactSelectHTML(int $selected = null) public static function getMessageContactSelectHTML(int $selected = null): string
{ {
$o = ''; $o = '';
@ -62,25 +62,7 @@ class ACL
$page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.css')); $page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.css'));
$page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput-typeahead.css')); $page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput-typeahead.css'));
$condition = [ $contacts = self::getValidMessageRecipientsForUser(local_user());
'uid' => local_user(),
'self' => false,
'blocked' => false,
'pending' => false,
'archive' => false,
'deleted' => false,
'rel' => [Contact::FOLLOWER, Contact::SHARING, Contact::FRIEND],
'network' => Protocol::SUPPORT_PRIVATE,
];
$contacts = Contact::selectToArray(
['id', 'name', 'addr', 'micro'],
DBA::mergeConditions($condition, ["`notify` != ''"])
);
$arr = ['contact' => $contacts, 'entry' => $o];
Hook::callAll(DI::args()->getModuleName() . '_pre_recipient', $arr);
$tpl = Renderer::getMarkupTemplate('acl/message_recipient.tpl'); $tpl = Renderer::getMarkupTemplate('acl/message_recipient.tpl');
$o = Renderer::replaceMacros($tpl, [ $o = Renderer::replaceMacros($tpl, [
@ -93,6 +75,25 @@ class ACL
return $o; return $o;
} }
public static function getValidMessageRecipientsForUser(int $uid): array
{
$condition = [
'uid' => $uid,
'self' => false,
'blocked' => false,
'pending' => false,
'archive' => false,
'deleted' => false,
'rel' => [Contact::FOLLOWER, Contact::SHARING, Contact::FRIEND],
'network' => Protocol::SUPPORT_PRIVATE,
];
return Contact::selectToArray(
['id', 'name', 'addr', 'micro', 'url', 'nick'],
DBA::mergeConditions($condition, ["`notify` != ''"])
);
}
/** /**
* Returns a minimal ACL block for self-only permissions * Returns a minimal ACL block for self-only permissions
* *

View File

@ -59,13 +59,13 @@ class RedisCache extends AbstractCache implements ICanCacheInMemory
$redis_pw = $config->get('system', 'redis_password'); $redis_pw = $config->get('system', 'redis_password');
$redis_db = $config->get('system', 'redis_db', 0); $redis_db = $config->get('system', 'redis_db', 0);
if (isset($redis_port) && !@$this->redis->connect($redis_host, $redis_port)) { if (!empty($redis_port) && !@$this->redis->connect($redis_host, $redis_port)) {
throw new CachePersistenceException('Expected Redis server at ' . $redis_host . ':' . $redis_port . ' isn\'t available'); throw new CachePersistenceException('Expected Redis server at ' . $redis_host . ':' . $redis_port . ' isn\'t available');
} elseif (!@$this->redis->connect($redis_host)) { } elseif (!@$this->redis->connect($redis_host)) {
throw new CachePersistenceException('Expected Redis server at ' . $redis_host . ' isn\'t available'); throw new CachePersistenceException('Expected Redis server at ' . $redis_host . ' isn\'t available');
} }
if (isset($redis_pw) && !$this->redis->auth($redis_pw)) { if (!empty($redis_pw) && !$this->redis->auth($redis_pw)) {
throw new CachePersistenceException('Cannot authenticate redis server at ' . $redis_host . ':' . $redis_port); throw new CachePersistenceException('Cannot authenticate redis server at ' . $redis_host . ':' . $redis_port);
} }

View File

@ -22,7 +22,6 @@
namespace Friendica\Core; namespace Friendica\Core;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\User; use Friendica\Model\User;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
use Friendica\Protocol\Activity; use Friendica\Protocol\Activity;
@ -171,15 +170,15 @@ class Protocol
} }
/** /**
* Sends an unfriend message. Does not remove the contact * Sends an unfollow message. Does not remove the contact
* *
* @param array $user User unfriending * @param array $contact Target public contact (uid = 0) array
* @param array $contact Contact unfriended * @param array $user Source local user array
* @return bool|null true if successful, false if not, null if no remote action was performed * @return bool|null true if successful, false if not, null if no remote action was performed
* @throws HTTPException\InternalServerErrorException * @throws HTTPException\InternalServerErrorException
* @throws \ImagickException * @throws \ImagickException
*/ */
public static function terminateFriendship(array $user, array $contact): ?bool public static function unfollow(array $contact, array $user): ?bool
{ {
if (empty($contact['network'])) { if (empty($contact['network'])) {
throw new \InvalidArgumentException('Missing network key in contact array'); throw new \InvalidArgumentException('Missing network key in contact array');
@ -216,7 +215,8 @@ class Protocol
// Catch-all hook for connector addons // Catch-all hook for connector addons
$hook_data = [ $hook_data = [
'contact' => $contact, 'contact' => $contact,
'result' => null 'uid' => $user['uid'],
'result' => null,
]; ];
Hook::callAll('unfollow', $hook_data); Hook::callAll('unfollow', $hook_data);
@ -226,12 +226,13 @@ class Protocol
/** /**
* Revoke an incoming follow from the provided contact * Revoke an incoming follow from the provided contact
* *
* @param array $contact Private contact (uid != 0) array * @param array $contact Target public contact (uid == 0) array
* @param int $uid Source local user id
* @return bool|null true if successful, false if not, null if no action was performed * @return bool|null true if successful, false if not, null if no action was performed
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException * @throws \ImagickException
*/ */
public static function revokeFollow(array $contact): ?bool public static function revokeFollow(array $contact, int $uid): ?bool
{ {
if (empty($contact['network'])) { if (empty($contact['network'])) {
throw new \InvalidArgumentException('Missing network key in contact array'); throw new \InvalidArgumentException('Missing network key in contact array');
@ -243,13 +244,14 @@ class Protocol
} }
if ($protocol == Protocol::ACTIVITYPUB) { if ($protocol == Protocol::ACTIVITYPUB) {
return ActivityPub\Transmitter::sendContactReject($contact['url'], $contact['hub-verify'], $contact['uid']); return ActivityPub\Transmitter::sendContactReject($contact['url'], $contact['hub-verify'], $uid);
} }
// Catch-all hook for connector addons // Catch-all hook for connector addons
$hook_data = [ $hook_data = [
'contact' => $contact, 'contact' => $contact,
'result' => null, 'uid' => $uid,
'result' => null,
]; ];
Hook::callAll('revoke_follow', $hook_data); Hook::callAll('revoke_follow', $hook_data);

View File

@ -334,9 +334,10 @@ class System
* and adds an application/json HTTP header to the output. * and adds an application/json HTTP header to the output.
* After finishing the process is getting killed. * After finishing the process is getting killed.
* *
* @param mixed $x The input content. * @param mixed $x The input content
* @param string $content_type Type of the input (Default: 'application/json'). * @param string $content_type Type of the input (Default: 'application/json')
* @param integer $options JSON options * @param integer $options JSON options
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/ */
public static function jsonExit($x, $content_type = 'application/json', int $options = 0) { public static function jsonExit($x, $content_type = 'application/json', int $options = 0) {
DI::apiResponse()->setType(Response::TYPE_JSON, $content_type); DI::apiResponse()->setType(Response::TYPE_JSON, $content_type);

View File

@ -1378,8 +1378,9 @@ class Worker
* Defers the current worker entry * Defers the current worker entry
* *
* @return boolean had the entry been deferred? * @return boolean had the entry been deferred?
* @throws \Exception
*/ */
public static function defer() public static function defer(): bool
{ {
$queue = DI::app()->getQueue(); $queue = DI::app()->getQueue();
@ -1387,7 +1388,6 @@ class Worker
return false; return false;
} }
$retrial = $queue['retrial'];
$id = $queue['id']; $id = $queue['id'];
$priority = $queue['priority']; $priority = $queue['priority'];

View File

@ -487,6 +487,11 @@ abstract class DI
return self::$dice->create(Contact\Introduction\Factory\Introduction::class); return self::$dice->create(Contact\Introduction\Factory\Introduction::class);
} }
public static function localRelationship(): Contact\LocalRelationship\Repository\LocalRelationship
{
return self::$dice->create(Contact\LocalRelationship\Repository\LocalRelationship::class);
}
public static function permissionSet(): Security\PermissionSet\Repository\PermissionSet public static function permissionSet(): Security\PermissionSet\Repository\PermissionSet
{ {
return self::$dice->create(Security\PermissionSet\Repository\PermissionSet::class); return self::$dice->create(Security\PermissionSet\Repository\PermissionSet::class);
@ -527,9 +532,14 @@ abstract class DI
return self::$dice->create(Navigation\Notifications\Factory\Notify::class); return self::$dice->create(Navigation\Notifications\Factory\Notify::class);
} }
public static function formattedNotificationFactory(): Navigation\Notifications\Factory\FormattedNotification public static function formattedNotificationFactory(): Navigation\Notifications\Factory\FormattedNotify
{ {
return self::$dice->create(Navigation\Notifications\Factory\FormattedNotification::class); return self::$dice->create(Navigation\Notifications\Factory\FormattedNotify::class);
}
public static function formattedNavNotificationFactory(): Navigation\Notifications\Factory\FormattedNavNotification
{
return self::$dice->create(Navigation\Notifications\Factory\FormattedNavNotification::class);
} }
// //

View File

@ -114,6 +114,7 @@ class Database
$pass = trim($this->configCache->get('database', 'password')); $pass = trim($this->configCache->get('database', 'password'));
$db = trim($this->configCache->get('database', 'database')); $db = trim($this->configCache->get('database', 'database'));
$charset = trim($this->configCache->get('database', 'charset')); $charset = trim($this->configCache->get('database', 'charset'));
$socket = trim($this->configCache->get('database', 'socket'));
if (!(strlen($server) && strlen($user))) { if (!(strlen($server) && strlen($user))) {
return false; return false;
@ -135,9 +136,14 @@ class Database
$connect .= ";charset=" . $charset; $connect .= ";charset=" . $charset;
} }
if ($socket) {
$connect .= ";$unix_socket=" . $socket;
}
try { try {
$this->connection = @new PDO($connect, $user, $pass, [PDO::ATTR_PERSISTENT => $persistent]); $this->connection = @new PDO($connect, $user, $pass, [PDO::ATTR_PERSISTENT => $persistent]);
$this->connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->pdo_emulate_prepares); $this->connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->pdo_emulate_prepares);
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
$this->connected = true; $this->connected = true;
} catch (PDOException $e) { } catch (PDOException $e) {
$this->connected = false; $this->connected = false;
@ -159,6 +165,11 @@ class Database
if ($charset) { if ($charset) {
$this->connection->set_charset($charset); $this->connection->set_charset($charset);
} }
if ($socket) {
$this->connection->set_socket($socket);
}
} }
} }

View File

@ -25,6 +25,7 @@ use Friendica\Core\Logger;
use Friendica\Core\Protocol; use Friendica\Core\Protocol;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Friendica\Model\Conversation;
use Friendica\Model\GServer; use Friendica\Model\GServer;
use Friendica\Model\Item; use Friendica\Model\Item;
use Friendica\Model\ItemURI; use Friendica\Model\ItemURI;
@ -33,6 +34,9 @@ use Friendica\Model\Post;
use Friendica\Model\Post\Category; use Friendica\Model\Post\Category;
use Friendica\Model\Tag; use Friendica\Model\Tag;
use Friendica\Model\Verb; use Friendica\Model\Verb;
use Friendica\Protocol\ActivityPub\Processor;
use Friendica\Protocol\ActivityPub\Receiver;
use Friendica\Util\JsonLD;
use Friendica\Util\Strings; use Friendica\Util\Strings;
/** /**
@ -46,7 +50,7 @@ class PostUpdate
// Needed for the helper function to read from the legacy term table // Needed for the helper function to read from the legacy term table
const OBJECT_TYPE_POST = 1; const OBJECT_TYPE_POST = 1;
const VERSION = 1427; const VERSION = 1452;
/** /**
* Calls the post update functions * Calls the post update functions
@ -104,6 +108,9 @@ class PostUpdate
if (!self::update1427()) { if (!self::update1427()) {
return false; return false;
} }
if (!self::update1452()) {
return false;
}
return true; return true;
} }
@ -1012,4 +1019,70 @@ class PostUpdate
return false; return false;
} }
/**
* Fill the receivers of the post via the raw source
*
* @return bool "true" when the job is done
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
private static function update1452()
{
// Was the script completed?
if (DI::config()->get('system', 'post_update_version') >= 1452) {
return true;
}
$id = DI::config()->get('system', 'post_update_version_1452_id', 0);
Logger::info('Start', ['uri-id' => $id]);
$rows = 0;
$received = '';
$conversations = DBA::p("SELECT `post-view`.`uri-id`, `conversation`.`source`, `conversation`.`received` FROM `conversation`
INNER JOIN `post-view` ON `post-view`.`uri` = `conversation`.`item-uri`
WHERE NOT `source` IS NULL AND `conversation`.`protocol` = ? AND `uri-id` > ? LIMIT ?",
Conversation::PARCEL_ACTIVITYPUB, $id, 1000);
if (DBA::errorNo() != 0) {
Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
return false;
}
while ($conversation = DBA::fetch($conversations)) {
$id = $conversation['uri-id'];
$received = $conversation['received'];
$raw = json_decode($conversation['source'], true);
if (empty($raw)) {
continue;
}
$activity = JsonLD::compact($raw);
$urls = Receiver::getReceiverURL($activity);
Processor::storeReceivers($conversation['uri-id'], $urls);
if (!empty($activity['as:object'])) {
$urls = array_merge($urls, Receiver::getReceiverURL($activity['as:object']));
Processor::storeReceivers($conversation['uri-id'], $urls);
}
++$rows;
}
DBA::close($conversations);
DI::config()->set('system', 'post_update_version_1452_id', $id);
Logger::info('Processed', ['rows' => $rows, 'last' => $id, 'last-received' => $received]);
if ($rows <= 100) {
DI::config()->set('system', 'post_update_version', 1452);
Logger::info('Done');
return true;
}
return false;
}
} }

View File

@ -27,6 +27,7 @@ use Friendica\Content\Text\BBCode;
use Friendica\Database\Database; use Friendica\Database\Database;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\Model\Post; use Friendica\Model\Post;
use Friendica\Model\Tag as TagModel;
use Friendica\Model\Verb; use Friendica\Model\Verb;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
use Friendica\Protocol\Activity; use Friendica\Protocol\Activity;
@ -76,8 +77,8 @@ class Status extends BaseFactory
*/ */
public function createFromUriId(int $uriId, $uid = 0): \Friendica\Object\Api\Mastodon\Status public function createFromUriId(int $uriId, $uid = 0): \Friendica\Object\Api\Mastodon\Status
{ {
$fields = ['uri-id', 'uid', 'author-id', 'author-link', 'starred', 'app', 'title', 'body', 'raw-body', 'created', 'network', $fields = ['uri-id', 'uid', 'author-id', 'author-link', 'starred', 'app', 'title', 'body', 'raw-body', 'content-warning',
'thr-parent-id', 'parent-author-id', 'language', 'uri', 'plink', 'private', 'vid', 'gravity']; 'created', 'network', 'thr-parent-id', 'parent-author-id', 'language', 'uri', 'plink', 'private', 'vid', 'gravity'];
$item = Post::selectFirst($fields, ['uri-id' => $uriId, 'uid' => [0, $uid]], ['order' => ['uid' => true]]); $item = Post::selectFirst($fields, ['uri-id' => $uriId, 'uid' => [0, $uid]], ['order' => ['uid' => true]]);
if (!$item) { if (!$item) {
$mail = DBA::selectFirst('mail', ['id'], ['uri-id' => $uriId, 'uid' => $uid]); $mail = DBA::selectFirst('mail', ['id'], ['uri-id' => $uriId, 'uid' => $uid]);
@ -127,7 +128,7 @@ class Status extends BaseFactory
Post\ThreadUser::getPinned($uriId, $uid) Post\ThreadUser::getPinned($uriId, $uid)
); );
$sensitive = $this->dba->exists('tag-view', ['uri-id' => $uriId, 'name' => 'nsfw']); $sensitive = $this->dba->exists('tag-view', ['uri-id' => $uriId, 'name' => 'nsfw', 'type' => TagModel::HASHTAG]);
$application = new \Friendica\Object\Api\Mastodon\Application($item['app'] ?: ContactSelector::networkToName($item['network'], $item['author-link'])); $application = new \Friendica\Object\Api\Mastodon\Application($item['app'] ?: ContactSelector::networkToName($item['network'], $item['author-link']));
$mentions = $this->mstdnMentionFactory->createFromUriId($uriId)->getArrayCopy(); $mentions = $this->mstdnMentionFactory->createFromUriId($uriId)->getArrayCopy();

View File

@ -27,6 +27,7 @@ use Friendica\Content\Text\HTML;
use Friendica\Database\Database; use Friendica\Database\Database;
use Friendica\Factory\Api\Friendica\Activities; use Friendica\Factory\Api\Friendica\Activities;
use Friendica\Factory\Api\Twitter\User as TwitterUser; use Friendica\Factory\Api\Twitter\User as TwitterUser;
use Friendica\Model\Item;
use Friendica\Model\Post; use Friendica\Model\Post;
use Friendica\Model\Verb; use Friendica\Model\Verb;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
@ -70,14 +71,15 @@ class Status extends BaseFactory
* @param int $uriId Uri-ID of the item * @param int $uriId Uri-ID of the item
* @param int $uid Item user * @param int $uid Item user
* *
* @return \Friendica\Object\Api\Mastodon\Status * @return \Friendica\Object\Api\Twitter\Status
* @throws HTTPException\InternalServerErrorException * @throws HTTPException\InternalServerErrorException
* @throws ImagickException|HTTPException\NotFoundException * @throws ImagickException|HTTPException\NotFoundException
*/ */
public function createFromItemId(int $id, int $uid, bool $include_entities = false): \Friendica\Object\Api\Twitter\Status public function createFromItemId(int $id, int $uid, bool $include_entities = false): \Friendica\Object\Api\Twitter\Status
{ {
$fields = ['id', 'parent', 'uri-id', 'uid', 'author-id', 'author-link', 'author-network', 'owner-id', 'starred', 'app', 'title', 'body', 'raw-body', 'created', 'network', $fields = ['parent-uri-id', 'uri-id', 'uid', 'author-id', 'author-link', 'author-network', 'owner-id', 'causer-id',
'thr-parent-id', 'parent-author-id', 'parent-author-nick', 'language', 'uri', 'plink', 'private', 'vid', 'gravity', 'coord']; 'starred', 'app', 'title', 'body', 'raw-body', 'created', 'network','post-reason', 'language', 'gravity',
'thr-parent-id', 'parent-author-id', 'parent-author-nick', 'uri', 'plink', 'private', 'vid', 'coord'];
$item = Post::selectFirst($fields, ['id' => $id], ['order' => ['uid' => true]]); $item = Post::selectFirst($fields, ['id' => $id], ['order' => ['uid' => true]]);
if (!$item) { if (!$item) {
throw new HTTPException\NotFoundException('Item with ID ' . $id . ' not found.'); throw new HTTPException\NotFoundException('Item with ID ' . $id . ' not found.');
@ -89,14 +91,15 @@ class Status extends BaseFactory
* @param int $uriId Uri-ID of the item * @param int $uriId Uri-ID of the item
* @param int $uid Item user * @param int $uid Item user
* *
* @return \Friendica\Object\Api\Mastodon\Status * @return \Friendica\Object\Api\Twitter\Status
* @throws HTTPException\InternalServerErrorException * @throws HTTPException\InternalServerErrorException
* @throws ImagickException|HTTPException\NotFoundException * @throws ImagickException|HTTPException\NotFoundException
*/ */
public function createFromUriId(int $uriId, $uid = 0, $include_entities = false): \Friendica\Object\Api\Twitter\Status public function createFromUriId(int $uriId, $uid = 0, $include_entities = false): \Friendica\Object\Api\Twitter\Status
{ {
$fields = ['id', 'parent', 'uri-id', 'uid', 'author-id', 'author-link', 'author-network', 'owner-id', 'starred', 'app', 'title', 'body', 'raw-body', 'created', 'network', $fields = ['parent-uri-id', 'uri-id', 'uid', 'author-id', 'author-link', 'author-network', 'owner-id', 'causer-id',
'thr-parent-id', 'parent-author-id', 'parent-author-nick', 'language', 'uri', 'plink', 'private', 'vid', 'gravity', 'coord']; 'starred', 'app', 'title', 'body', 'raw-body', 'created', 'network','post-reason', 'language', 'gravity',
'thr-parent-id', 'parent-author-id', 'parent-author-nick', 'uri', 'plink', 'private', 'vid', 'coord'];
$item = Post::selectFirst($fields, ['uri-id' => $uriId, 'uid' => [0, $uid]], ['order' => ['uid' => true]]); $item = Post::selectFirst($fields, ['uri-id' => $uriId, 'uid' => [0, $uid]], ['order' => ['uid' => true]]);
if (!$item) { if (!$item) {
throw new HTTPException\NotFoundException('Item with URI ID ' . $uriId . ' not found' . ($uid ? ' for user ' . $uid : '.')); throw new HTTPException\NotFoundException('Item with URI ID ' . $uriId . ' not found' . ($uid ? ' for user ' . $uid : '.'));
@ -108,14 +111,19 @@ class Status extends BaseFactory
* @param array $item item array * @param array $item item array
* @param int $uid Item user * @param int $uid Item user
* *
* @return \Friendica\Object\Api\Mastodon\Status * @return \Friendica\Object\Api\Twitter\Status
* @throws HTTPException\InternalServerErrorException * @throws HTTPException\InternalServerErrorException
* @throws ImagickException|HTTPException\NotFoundException * @throws ImagickException|HTTPException\NotFoundException
*/ */
private function createFromArray(array $item, int $uid, bool $include_entities): \Friendica\Object\Api\Twitter\Status private function createFromArray(array $item, int $uid, bool $include_entities): \Friendica\Object\Api\Twitter\Status
{ {
$author = $this->twitterUser->createFromContactId($item['author-id'], $uid, true); $author = $this->twitterUser->createFromContactId($item['author-id'], $uid, true);
$owner = $this->twitterUser->createFromContactId($item['owner-id'], $uid, true);
if (!empty($item['causer-id']) && ($item['post-reason'] == Item::PR_ANNOUNCEMENT)) {
$owner = $this->twitterUser->createFromContactId($item['causer-id'], $uid, true);
} else {
$owner = $this->twitterUser->createFromContactId($item['owner-id'], $uid, true);
}
$friendica_comments = Post::countPosts(['thr-parent-id' => $item['uri-id'], 'deleted' => false, 'gravity' => GRAVITY_COMMENT]); $friendica_comments = Post::countPosts(['thr-parent-id' => $item['uri-id'], 'deleted' => false, 'gravity' => GRAVITY_COMMENT]);

View File

@ -168,7 +168,7 @@ class APContact
// Detect multiple fast repeating request to the same address // Detect multiple fast repeating request to the same address
// See https://github.com/friendica/friendica/issues/9303 // See https://github.com/friendica/friendica/issues/9303
$cachekey = 'apcontact:getByURL:' . $url; $cachekey = 'apcontact:' . ItemURI::getIdByURI($url);
$result = DI::cache()->get($cachekey); $result = DI::cache()->get($cachekey);
if (!is_null($result)) { if (!is_null($result)) {
Logger::notice('Multiple requests for the address', ['url' => $url, 'update' => $update, 'callstack' => System::callstack(20), 'result' => $result]); Logger::notice('Multiple requests for the address', ['url' => $url, 'update' => $update, 'callstack' => System::callstack(20), 'result' => $result]);

View File

@ -685,7 +685,7 @@ class Contact
*/ */
public static function updateSelfFromUserID($uid, $update_avatar = false) public static function updateSelfFromUserID($uid, $update_avatar = false)
{ {
$fields = ['id', 'name', 'nick', 'location', 'about', 'keywords', 'avatar', 'prvkey', 'pubkey', $fields = ['id', 'name', 'nick', 'location', 'about', 'keywords', 'avatar', 'prvkey', 'pubkey', 'manually-approve',
'xmpp', 'matrix', 'contact-type', 'forum', 'prv', 'avatar-date', 'url', 'nurl', 'unsearchable', 'xmpp', 'matrix', 'contact-type', 'forum', 'prv', 'avatar-date', 'url', 'nurl', 'unsearchable',
'photo', 'thumb', 'micro', 'header', 'addr', 'request', 'notify', 'poll', 'confirm', 'poco', 'network']; 'photo', 'thumb', 'micro', 'header', 'addr', 'request', 'notify', 'poll', 'confirm', 'poco', 'network'];
$self = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]); $self = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]);
@ -757,6 +757,7 @@ class Contact
$fields['forum'] = $user['page-flags'] == User::PAGE_FLAGS_COMMUNITY; $fields['forum'] = $user['page-flags'] == User::PAGE_FLAGS_COMMUNITY;
$fields['prv'] = $user['page-flags'] == User::PAGE_FLAGS_PRVGROUP; $fields['prv'] = $user['page-flags'] == User::PAGE_FLAGS_PRVGROUP;
$fields['unsearchable'] = !$profile['net-publish']; $fields['unsearchable'] = !$profile['net-publish'];
$fields['manually-approve'] = in_array($user['page-flags'], [User::PAGE_FLAGS_NORMAL, User::PAGE_FLAGS_PRVGROUP]);
$update = false; $update = false;
@ -812,37 +813,13 @@ class Contact
} }
/** /**
* Sends an unfriend message. Removes the contact for two-way unfriending or sharing only protocols (feed an mail) * Unfollow the remote contact
* *
* @param array $user User unfriending * @param array $contact Target user-specific contact (uid != 0) array
* @param array $contact Contact (uid != 0) unfriended
* @param boolean $two_way Revoke eventual inbound follow as well
* @return bool|null true if successful, false if not, null if no remote action was performed
* @throws HTTPException\InternalServerErrorException * @throws HTTPException\InternalServerErrorException
* @throws \ImagickException * @throws \ImagickException
*/ */
public static function terminateFriendship(array $user, array $contact): ?bool public static function unfollow(array $contact): void
{
$result = Protocol::terminateFriendship($user, $contact);
if ($contact['rel'] == Contact::SHARING || in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) {
self::remove($contact['id']);
} else {
self::update(['rel' => Contact::FOLLOWER], ['id' => $contact['id']]);
}
return $result;
}
/**
* Revoke follow privileges of the remote user contact
*
* @param array $contact Contact unfriended
* @return bool|null Whether the remote operation is successful or null if no remote operation was performed
* @throws HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function revokeFollow(array $contact): ?bool
{ {
if (empty($contact['network'])) { if (empty($contact['network'])) {
throw new \InvalidArgumentException('Empty network in contact array'); throw new \InvalidArgumentException('Empty network in contact array');
@ -852,19 +829,69 @@ class Contact
throw new \InvalidArgumentException('Unexpected public contact record'); throw new \InvalidArgumentException('Unexpected public contact record');
} }
$result = Protocol::revokeFollow($contact); if (in_array($contact['rel'], [self::SHARING, self::FRIEND])) {
$cdata = Contact::getPublicAndUserContactID($contact['id'], $contact['uid']);
// A null value here means the remote network doesn't support explicit follow revocation, we can still Worker::add(PRIORITY_HIGH, 'Contact\Unfollow', $cdata['public'], $contact['uid']);
// break the locally recorded relationship
if ($result !== false) {
if ($contact['rel'] == self::FRIEND) {
self::update(['rel' => self::SHARING], ['id' => $contact['id']]);
} else {
self::remove($contact['id']);
}
} }
return $result; self::removeSharer($contact);
}
/**
* Revoke follow privileges of the remote user contact
*
* The local relationship is updated immediately, the eventual remote server is messaged in the background.
*
* @param array $contact User-specific contact array (uid != 0) to revoke the follow from
* @throws HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function revokeFollow(array $contact): void
{
if (empty($contact['network'])) {
throw new \InvalidArgumentException('Empty network in contact array');
}
if (empty($contact['uid'])) {
throw new \InvalidArgumentException('Unexpected public contact record');
}
if (in_array($contact['rel'], [self::FOLLOWER, self::FRIEND])) {
$cdata = Contact::getPublicAndUserContactID($contact['id'], $contact['uid']);
Worker::add(PRIORITY_HIGH, 'Contact\RevokeFollow', $cdata['public'], $contact['uid']);
}
self::removeFollower($contact);
}
/**
* Completely severs a relationship with a contact
*
* @param array $contact User-specific contact (uid != 0) array
* @throws HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function terminateFriendship(array $contact)
{
if (empty($contact['network'])) {
throw new \InvalidArgumentException('Empty network in contact array');
}
if (empty($contact['uid'])) {
throw new \InvalidArgumentException('Unexpected public contact record');
}
$cdata = Contact::getPublicAndUserContactID($contact['id'], $contact['uid']);
if (in_array($contact['rel'], [self::SHARING, self::FRIEND])) {
Worker::add(PRIORITY_HIGH, 'Contact\Unfollow', $cdata['public'], $contact['uid']);
}
if (in_array($contact['rel'], [self::FOLLOWER, self::FRIEND])) {
Worker::add(PRIORITY_HIGH, 'Contact\RevokeFollow', $cdata['public'], $contact['uid']);
}
self::remove($contact['id']);
} }
@ -1457,34 +1484,11 @@ class Contact
* *
* The function can be called with either the user or the contact array * The function can be called with either the user or the contact array
* *
* @param array $contact contact or user array * @param int $type type of contact or account
* @return string * @return string
*/ */
public static function getAccountType(array $contact) public static function getAccountType(int $type)
{ {
// There are several fields that indicate that the contact or user is a forum
// "page-flags" is a field in the user table,
// "forum" and "prv" are used in the contact table. They stand for User::PAGE_FLAGS_COMMUNITY and User::PAGE_FLAGS_PRVGROUP.
if ((isset($contact['page-flags']) && (intval($contact['page-flags']) == User::PAGE_FLAGS_COMMUNITY))
|| (isset($contact['page-flags']) && (intval($contact['page-flags']) == User::PAGE_FLAGS_PRVGROUP))
|| (isset($contact['forum']) && intval($contact['forum']))
|| (isset($contact['prv']) && intval($contact['prv']))
|| (isset($contact['community']) && intval($contact['community']))
) {
$type = self::TYPE_COMMUNITY;
} else {
$type = self::TYPE_PERSON;
}
// The "contact-type" (contact table) and "account-type" (user table) are more general then the chaos from above.
if (isset($contact["contact-type"])) {
$type = $contact["contact-type"];
}
if (isset($contact["account-type"])) {
$type = $contact["account-type"];
}
switch ($type) { switch ($type) {
case self::TYPE_ORGANISATION: case self::TYPE_ORGANISATION:
$account_type = DI::l10n()->t("Organisation"); $account_type = DI::l10n()->t("Organisation");
@ -2596,28 +2600,6 @@ class Contact
return $result; return $result;
} }
/**
* Unfollow a contact
*
* @param int $cid Public contact id
* @param int $uid User ID
*
* @return bool "true" if unfollowing had been successful
*/
public static function unfollow(int $cid, int $uid)
{
$cdata = self::getPublicAndUserContactID($cid, $uid);
if (empty($cdata['user'])) {
return false;
}
$contact = self::getById($cdata['user']);
self::removeSharer([], $contact);
return true;
}
/** /**
* @param array $importer Owner (local user) data * @param array $importer Owner (local user) data
* @param array $contact Existing owner-specific contact data we want to expand the relationship with. Optional. * @param array $contact Existing owner-specific contact data we want to expand the relationship with. Optional.
@ -2635,7 +2617,7 @@ class Contact
return false; return false;
} }
$fields = ['url', 'name', 'nick', 'avatar', 'photo', 'network', 'blocked']; $fields = ['id', 'url', 'name', 'nick', 'avatar', 'photo', 'network', 'blocked'];
$pub_contact = DBA::selectFirst('contact', $fields, ['id' => $datarray['author-id']]); $pub_contact = DBA::selectFirst('contact', $fields, ['id' => $datarray['author-id']]);
if (!DBA::isResult($pub_contact)) { if (!DBA::isResult($pub_contact)) {
// Should never happen // Should never happen
@ -2683,7 +2665,7 @@ class Contact
// Ensure to always have the correct network type, independent from the connection request method // Ensure to always have the correct network type, independent from the connection request method
self::updateFromProbe($contact['id']); self::updateFromProbe($contact['id']);
Post\UserNotification::insertNotification($contact['id'], Activity::FOLLOW, $importer['uid']); Post\UserNotification::insertNotification($pub_contact['id'], Activity::FOLLOW, $importer['uid']);
return true; return true;
} else { } else {
@ -2714,7 +2696,7 @@ class Contact
self::updateAvatar($contact_id, $photo, true); self::updateAvatar($contact_id, $photo, true);
Post\UserNotification::insertNotification($contact_id, Activity::FOLLOW, $importer['uid']); Post\UserNotification::insertNotification($pub_contact['id'], Activity::FOLLOW, $importer['uid']);
$contact_record = DBA::selectFirst('contact', ['id', 'network', 'name', 'url', 'photo'], ['id' => $contact_id]); $contact_record = DBA::selectFirst('contact', ['id', 'network', 'name', 'url', 'photo'], ['id' => $contact_id]);
@ -2734,9 +2716,7 @@ class Contact
Group::addMember(User::getDefaultGroup($importer['uid']), $contact_record['id']); Group::addMember(User::getDefaultGroup($importer['uid']), $contact_record['id']);
if (($user['notify-flags'] & Notification\Type::INTRO) && if (($user['notify-flags'] & Notification\Type::INTRO) && $user['page-flags'] == User::PAGE_FLAGS_NORMAL) {
in_array($user['page-flags'], [User::PAGE_FLAGS_NORMAL])) {
DI::notify()->createFromArray([ DI::notify()->createFromArray([
'type' => Notification\Type::INTRO, 'type' => Notification\Type::INTRO,
'otype' => Notification\ObjectType::INTRO, 'otype' => Notification\ObjectType::INTRO,
@ -2766,23 +2746,41 @@ class Contact
return null; return null;
} }
/**
* Update the local relationship when a local user loses a follower
*
* @param array $contact User-specific contact (uid != 0) array
* @throws HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function removeFollower(array $contact) public static function removeFollower(array $contact)
{ {
if (in_array($contact['rel'] ?? [], [self::FRIEND, self::SHARING])) { if (in_array($contact['rel'] ?? [], [self::FRIEND, self::SHARING])) {
DBA::update('contact', ['rel' => self::SHARING], ['id' => $contact['id']]); self::update(['rel' => self::SHARING], ['id' => $contact['id']]);
} elseif (!empty($contact['id'])) { } elseif (!empty($contact['id'])) {
self::remove($contact['id']); self::remove($contact['id']);
} else { } else {
DI::logger()->info('Couldn\'t remove follower because of invalid contact array', ['contact' => $contact, 'callstack' => System::callstack()]); DI::logger()->info('Couldn\'t remove follower because of invalid contact array', ['contact' => $contact, 'callstack' => System::callstack()]);
} }
$cdata = Contact::getPublicAndUserContactID($contact['id'], $contact['uid']);
DI::notification()->deleteForUserByVerb($contact['uid'], Activity::FOLLOW, ['actor-id' => $cdata['public']]);
} }
public static function removeSharer($importer, $contact) /**
* Update the local relationship when a local user unfollow a contact.
* Removes the contact for sharing-only protocols (feed and mail).
*
* @param array $contact User-specific contact (uid != 0) array
* @throws HTTPException\InternalServerErrorException
*/
public static function removeSharer(array $contact)
{ {
if (($contact['rel'] == self::FRIEND) || ($contact['rel'] == self::FOLLOWER)) { if ($contact['rel'] == self::SHARING || in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) {
self::update(['rel' => self::FOLLOWER], ['id' => $contact['id']]);
} else {
self::remove($contact['id']); self::remove($contact['id']);
} else {
self::update(['rel' => self::FOLLOWER], ['id' => $contact['id']]);
} }
} }
@ -2947,7 +2945,7 @@ class Contact
*/ */
public static function isForum($contactid) public static function isForum($contactid)
{ {
$fields = ['contact-type', 'forum', 'prv']; $fields = ['contact-type'];
$condition = ['id' => $contactid]; $condition = ['id' => $contactid];
$contact = DBA::selectFirst('contact', $fields, $condition); $contact = DBA::selectFirst('contact', $fields, $condition);
if (!DBA::isResult($contact)) { if (!DBA::isResult($contact)) {
@ -2955,7 +2953,7 @@ class Contact
} }
// Is it a forum? // Is it a forum?
return (($contact['contact-type'] == self::TYPE_COMMUNITY) || $contact['forum'] || $contact['prv']); return ($contact['contact-type'] == self::TYPE_COMMUNITY);
} }
/** /**

View File

@ -546,6 +546,22 @@ class GServer
Logger::info('Update registered users', ['id' => $id, 'url' => $serverdata['nurl'], 'registered-users' => $max_users]); Logger::info('Update registered users', ['id' => $id, 'url' => $serverdata['nurl'], 'registered-users' => $max_users]);
DBA::update('gserver', ['registered-users' => $max_users], ['id' => $id]); DBA::update('gserver', ['registered-users' => $max_users], ['id' => $id]);
} }
if (empty($serverdata['active-month-users'])) {
$contacts = DBA::count('contact', ["`uid` = ? AND `gsid` = ? AND NOT `failed` AND `last-item` > ?", 0, $id, DateTimeFormat::utc('now - 30 days')]);
if ($contacts > 0) {
Logger::info('Update monthly users', ['id' => $id, 'url' => $serverdata['nurl'], 'monthly-users' => $contacts]);
DBA::update('gserver', ['active-month-users' => $contacts], ['id' => $id]);
}
}
if (empty($serverdata['active-halfyear-users'])) {
$contacts = DBA::count('contact', ["`uid` = ? AND `gsid` = ? AND NOT `failed` AND `last-item` > ?", 0, $id, DateTimeFormat::utc('now - 180 days')]);
if ($contacts > 0) {
Logger::info('Update halfyear users', ['id' => $id, 'url' => $serverdata['nurl'], 'halfyear-users' => $contacts]);
DBA::update('gserver', ['active-halfyear-users' => $contacts], ['id' => $id]);
}
}
} }
if (!empty($serverdata['network']) && in_array($serverdata['network'], [Protocol::DFRN, Protocol::DIASPORA])) { if (!empty($serverdata['network']) && in_array($serverdata['network'], [Protocol::DFRN, Protocol::DIASPORA])) {

View File

@ -29,6 +29,7 @@ use Friendica\Database\Database;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\DI; use Friendica\DI;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
use Friendica\Protocol\ActivityPub;
/** /**
* functions for interacting with the group database table * functions for interacting with the group database table
@ -40,7 +41,7 @@ class Group
public static function getByUserId($uid, $includesDeleted = false) public static function getByUserId($uid, $includesDeleted = false)
{ {
$conditions = ['uid' => $uid]; $conditions = ['uid' => $uid, 'cid' => null];
if (!$includesDeleted) { if (!$includesDeleted) {
$conditions['deleted'] = false; $conditions['deleted'] = false;
@ -309,6 +310,68 @@ class Group
return DBA::delete('group_member', ['gid' => $gid, 'contact-id' => $cid]); return DBA::delete('group_member', ['gid' => $gid, 'contact-id' => $cid]);
} }
/**
* Adds contacts to a group
*
* @param int $gid
* @param array $contacts
* @throws \Exception
*/
public static function addMembers(int $gid, array $contacts)
{
if (!$gid || !$contacts) {
return false;
}
// @TODO Backward compatibility with user contacts, remove by version 2022.03
$group = DBA::selectFirst('group', ['uid'], ['id' => $gid]);
if (empty($group)) {
throw new HTTPException\NotFoundException('Group not found.');
}
foreach ($contacts as $cid) {
$cdata = Contact::getPublicAndUserContactID($cid, $group['uid']);
if (empty($cdata['user'])) {
throw new HTTPException\NotFoundException('Invalid contact.');
}
DBA::insert('group_member', ['gid' => $gid, 'contact-id' => $cdata['user']], Database::INSERT_IGNORE);
}
}
/**
* Removes contacts from a group
*
* @param int $gid
* @param array $contacts
* @throws \Exception
*/
public static function removeMembers(int $gid, array $contacts)
{
if (!$gid || !$contacts) {
return false;
}
// @TODO Backward compatibility with user contacts, remove by version 2022.03
$group = DBA::selectFirst('group', ['uid'], ['id' => $gid]);
if (empty($group)) {
throw new HTTPException\NotFoundException('Group not found.');
}
$contactIds = [];
foreach ($contacts as $cid) {
$cdata = Contact::getPublicAndUserContactID($cid, $group['uid']);
if (empty($cdata['user'])) {
throw new HTTPException\NotFoundException('Invalid contact.');
}
$contactIds[] = $cdata['user'];
}
DBA::delete('group_member', ['gid' => $gid, 'contact-id' => $contactIds]);
}
/** /**
* Returns the combined list of contact ids from a group id list * Returns the combined list of contact ids from a group id list
* *
@ -407,7 +470,7 @@ class Group
] ]
]; ];
$stmt = DBA::select('group', [], ['deleted' => 0, 'uid' => $uid], ['order' => ['name']]); $stmt = DBA::select('group', [], ['deleted' => false, 'uid' => $uid, 'cid' => null], ['order' => ['name']]);
while ($group = DBA::fetch($stmt)) { while ($group = DBA::fetch($stmt)) {
$display_groups[] = [ $display_groups[] = [
'name' => $group['name'], 'name' => $group['name'],
@ -464,7 +527,7 @@ class Group
$member_of = self::getIdsByContactId($cid); $member_of = self::getIdsByContactId($cid);
} }
$stmt = DBA::select('group', [], ['deleted' => 0, 'uid' => local_user()], ['order' => ['name']]); $stmt = DBA::select('group', [], ['deleted' => false, 'uid' => local_user(), 'cid' => null], ['order' => ['name']]);
while ($group = DBA::fetch($stmt)) { while ($group = DBA::fetch($stmt)) {
$selected = (($group_id == $group['id']) ? ' group-selected' : ''); $selected = (($group_id == $group['id']) ? ' group-selected' : '');
@ -519,4 +582,79 @@ class Group
return $o; return $o;
} }
/**
* Fetch the group id for the given contact id
*
* @param integer $id Contact ID
* @return integer Group IO
*/
public static function getIdForForum(int $id)
{
Logger::info('Get id for forum id', ['id' => $id]);
$contact = Contact::getById($id, ['uid', 'name', 'contact-type', 'manually-approve']);
if (empty($contact) || ($contact['contact-type'] != Contact::TYPE_COMMUNITY) || !$contact['manually-approve']) {
return 0;
}
$group = DBA::selectFirst('group', ['id'], ['uid' => $contact['uid'], 'cid' => $id]);
if (empty($group)) {
$fields = [
'uid' => $contact['uid'],
'name' => $contact['name'],
'cid' => $id,
];
DBA::insert('group', $fields);
$gid = DBA::lastInsertId();
} else {
$gid = $group['id'];
}
return $gid;
}
/**
* Fetch the followers of a given contact id and store them as group members
*
* @param integer $id Contact ID
*/
public static function updateMembersForForum(int $id)
{
Logger::info('Update forum members', ['id' => $id]);
$contact = Contact::getById($id, ['uid', 'url']);
if (empty($contact)) {
return;
}
$apcontact = APContact::getByURL($contact['url']);
if (empty($apcontact['followers'])) {
return;
}
$gid = self::getIdForForum($id);
if (empty($gid)) {
return;
}
$group_members = DBA::selectToArray('group_member', ['contact-id'], ['gid' => $gid]);
if (!empty($group_members)) {
$current = array_unique(array_column($group_members, 'contact-id'));
} else {
$current = [];
}
foreach (ActivityPub::fetchItems($apcontact['followers'], $contact['uid']) as $follower) {
$id = Contact::getIdForURL($follower);
if (!in_array($id, $current)) {
DBA::insert('group_member', ['gid' => $gid, 'contact-id' => $id]);
} else {
$key = array_search($id, $current);
unset($current[$key]);
}
}
DBA::delete('group_member', ['gid' => $gid, 'contact-id' => $current]);
Logger::info('Updated forum members', ['id' => $id, 'count' => DBA::count('group_member', ['gid' => $gid])]);
}
} }

View File

@ -74,6 +74,11 @@ class Item
const PR_RELAY = 74; const PR_RELAY = 74;
const PR_FETCHED = 75; const PR_FETCHED = 75;
// system.accept_only_sharer setting values
const COMPLETION_NONE = 1;
const COMPLETION_COMMENT = 0;
const COMPLETION_LIKE = 2;
// Field list that is used to display the items // Field list that is used to display the items
const DISPLAY_FIELDLIST = [ const DISPLAY_FIELDLIST = [
'uid', 'id', 'parent', 'guid', 'network', 'gravity', 'uid', 'id', 'parent', 'guid', 'network', 'gravity',
@ -100,7 +105,7 @@ class Item
'inform', 'deleted', 'extid', 'post-type', 'post-reason', 'gravity', 'inform', 'deleted', 'extid', 'post-type', 'post-reason', 'gravity',
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid',
'author-id', 'author-link', 'author-name', 'author-avatar', 'owner-id', 'owner-link', 'contact-uid', 'author-id', 'author-link', 'author-name', 'author-avatar', 'owner-id', 'owner-link', 'contact-uid',
'signed_text', 'network', 'wall', 'contact-id', 'plink', 'forum_mode', 'origin', 'signed_text', 'network', 'wall', 'contact-id', 'plink', 'origin',
'thr-parent-id', 'parent-uri-id', 'postopts', 'pubmail', 'thr-parent-id', 'parent-uri-id', 'postopts', 'pubmail',
'event-created', 'event-edited', 'event-start', 'event-finish', 'event-created', 'event-edited', 'event-start', 'event-finish',
'event-summary', 'event-desc', 'event-location', 'event-type', 'event-summary', 'event-desc', 'event-location', 'event-type',
@ -114,7 +119,7 @@ class Item
'postopts', 'plink', 'resource-id', 'event-id', 'inform', 'postopts', 'plink', 'resource-id', 'event-id', 'inform',
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'post-type', 'post-reason', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'post-type', 'post-reason',
'private', 'pubmail', 'visible', 'starred', 'private', 'pubmail', 'visible', 'starred',
'unseen', 'deleted', 'origin', 'forum_mode', 'mention', 'global', 'network', 'unseen', 'deleted', 'origin', 'mention', 'global', 'network',
'title', 'content-warning', 'body', 'location', 'coord', 'app', 'title', 'content-warning', 'body', 'location', 'coord', 'app',
'rendered-hash', 'rendered-html', 'object-type', 'object', 'target-type', 'target', 'rendered-hash', 'rendered-html', 'object-type', 'object', 'target-type', 'target',
'author-id', 'author-link', 'author-name', 'author-avatar', 'author-network', 'author-id', 'author-link', 'author-name', 'author-avatar', 'author-network',
@ -655,7 +660,7 @@ class Item
$fields = ['uid', 'uri', 'parent-uri', 'id', 'deleted', $fields = ['uid', 'uri', 'parent-uri', 'id', 'deleted',
'uri-id', 'parent-uri-id', 'uri-id', 'parent-uri-id',
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid',
'wall', 'private', 'forum_mode', 'origin', 'author-id']; 'wall', 'private', 'origin', 'author-id'];
$condition = ['uri-id' => $item['thr-parent-id'], 'uid' => $item['uid']]; $condition = ['uri-id' => $item['thr-parent-id'], 'uid' => $item['uid']];
$params = ['order' => ['id' => false]]; $params = ['order' => ['id' => false]];
$parent = Post::selectFirst($fields, $condition, $params); $parent = Post::selectFirst($fields, $condition, $params);
@ -818,6 +823,15 @@ class Item
$item['inform'] = trim($item['inform'] ?? ''); $item['inform'] = trim($item['inform'] ?? '');
$item['file'] = trim($item['file'] ?? ''); $item['file'] = trim($item['file'] ?? '');
// Communities aren't working with the Diaspora protoccol
if (($uid != 0) && ($item['network'] == Protocol::DIASPORA)) {
$user = User::getById($uid, ['account-type']);
if ($user['account-type'] == Contact::TYPE_COMMUNITY) {
Logger::info('Community posts are not supported via Diaspora');
return 0;
}
}
// Items cannot be stored before they happen ... // Items cannot be stored before they happen ...
if ($item['created'] > DateTimeFormat::utcNow()) { if ($item['created'] > DateTimeFormat::utcNow()) {
$item['created'] = DateTimeFormat::utcNow(); $item['created'] = DateTimeFormat::utcNow();
@ -881,10 +895,15 @@ class Item
$item['parent-uri'] = $toplevel_parent['uri']; $item['parent-uri'] = $toplevel_parent['uri'];
$item['parent-uri-id'] = $toplevel_parent['uri-id']; $item['parent-uri-id'] = $toplevel_parent['uri-id'];
$item['deleted'] = $toplevel_parent['deleted']; $item['deleted'] = $toplevel_parent['deleted'];
$item['allow_cid'] = $toplevel_parent['allow_cid'];
$item['allow_gid'] = $toplevel_parent['allow_gid']; // Reshares have to keep their permissions to allow forums to work
$item['deny_cid'] = $toplevel_parent['deny_cid']; if (!$item['origin'] || ($item['verb'] != Activity::ANNOUNCE)) {
$item['deny_gid'] = $toplevel_parent['deny_gid']; $item['allow_cid'] = $toplevel_parent['allow_cid'];
$item['allow_gid'] = $toplevel_parent['allow_gid'];
$item['deny_cid'] = $toplevel_parent['deny_cid'];
$item['deny_gid'] = $toplevel_parent['deny_gid'];
}
$parent_origin = $toplevel_parent['origin']; $parent_origin = $toplevel_parent['origin'];
// Don't federate received participation messages // Don't federate received participation messages
@ -905,15 +924,6 @@ class Item
$item['private'] = $toplevel_parent['private']; $item['private'] = $toplevel_parent['private'];
} }
/*
* Edge case. We host a public forum that was originally posted to privately.
* The original author commented, but as this is a comment, the permissions
* weren't fixed up so it will still show the comment as private unless we fix it here.
*/
if ((intval($toplevel_parent['forum_mode']) == 1) && ($toplevel_parent['private'] != self::PUBLIC)) {
$item['private'] = self::PUBLIC;
}
// If its a post that originated here then tag the thread as "mention" // If its a post that originated here then tag the thread as "mention"
if ($item['origin'] && $item['uid']) { if ($item['origin'] && $item['uid']) {
DBA::update('post-thread-user', ['mention' => true], ['uri-id' => $item['parent-uri-id'], 'uid' => $item['uid']]); DBA::update('post-thread-user', ['mention' => true], ['uri-id' => $item['parent-uri-id'], 'uid' => $item['uid']]);
@ -1066,6 +1076,13 @@ class Item
unset($item['causer-id']); unset($item['causer-id']);
} }
if (in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN])) {
$content_warning = BBCode::getAbstract($item['body'], Protocol::ACTIVITYPUB);
if (!empty($content_warning) && empty($item['content-warning'])) {
$item['content-warning'] = $content_warning;
}
}
Post::insert($item['uri-id'], $item); Post::insert($item['uri-id'], $item);
if ($item['gravity'] == GRAVITY_PARENT) { if ($item['gravity'] == GRAVITY_PARENT) {
@ -1226,8 +1243,11 @@ class Item
return; return;
} }
$self_contact = Contact::selectFirst(['id'], ['uid' => $item['uid'], 'self' => true]);
$self = !empty($self_contact) ? $self_contact['id'] : 0;
$cid = Contact::getIdForURL($author['url'], $item['uid']); $cid = Contact::getIdForURL($author['url'], $item['uid']);
if (empty($cid) || !Contact::isSharing($cid, $item['uid'])) { if (empty($cid) || (!Contact::isSharing($cid, $item['uid']) && ($cid != $self))) {
Logger::info('The resharer is not a following contact: quit', ['resharer' => $author['url'], 'uid' => $item['uid'], 'cid' => $cid]); Logger::info('The resharer is not a following contact: quit', ['resharer' => $author['url'], 'uid' => $item['uid'], 'cid' => $cid]);
return; return;
} }
@ -1398,7 +1418,7 @@ class Item
$is_reshare = ($item['gravity'] == GRAVITY_ACTIVITY) && ($item['verb'] == Activity::ANNOUNCE); $is_reshare = ($item['gravity'] == GRAVITY_ACTIVITY) && ($item['verb'] == Activity::ANNOUNCE);
if ((($item['gravity'] == GRAVITY_PARENT) || $is_reshare) && if ((($item['gravity'] == GRAVITY_PARENT) || $is_reshare) &&
DI::pConfig()->get($uid, 'system', 'accept_only_sharer') && DI::pConfig()->get($uid, 'system', 'accept_only_sharer') == self::COMPLETION_NONE &&
!Contact::isSharingByURL($item['author-link'], $uid) && !Contact::isSharingByURL($item['author-link'], $uid) &&
!Contact::isSharingByURL($item['owner-link'], $uid)) { !Contact::isSharingByURL($item['owner-link'], $uid)) {
Logger::info('Contact is not a follower, thread will not be stored', ['author' => $item['author-link'], 'uid' => $uid]); Logger::info('Contact is not a follower, thread will not be stored', ['author' => $item['author-link'], 'uid' => $uid]);
@ -1406,9 +1426,15 @@ class Item
} }
if ((($item['gravity'] == GRAVITY_COMMENT) || $is_reshare) && !Post::exists(['uri-id' => $item['thr-parent-id'], 'uid' => $uid])) { if ((($item['gravity'] == GRAVITY_COMMENT) || $is_reshare) && !Post::exists(['uri-id' => $item['thr-parent-id'], 'uid' => $uid])) {
// Only do an auto complete with the source uid "0" to prevent privavy problems // Fetch the origin user for the post
$origin_uid = self::GetOriginUidForUriId($item['thr-parent-id'], $uid);
if (is_null($origin_uid)) {
Logger::info('Origin item was not found', ['uid' => $uid, 'uri-id' => $item['thr-parent-id']]);
return 0;
}
$causer = $item['causer-id'] ?: $item['author-id']; $causer = $item['causer-id'] ?: $item['author-id'];
$result = self::storeForUserByUriId($item['thr-parent-id'], $uid, ['causer-id' => $causer, 'post-reason' => self::PR_FETCHED]); $result = self::storeForUserByUriId($item['thr-parent-id'], $uid, ['causer-id' => $causer, 'post-reason' => self::PR_FETCHED], $origin_uid);
Logger::info('Fetched thread parent', ['uri-id' => $item['thr-parent-id'], 'uid' => $uid, 'causer' => $causer, 'result' => $result]); Logger::info('Fetched thread parent', ['uri-id' => $item['thr-parent-id'], 'uid' => $uid, 'causer' => $causer, 'result' => $result]);
} }
@ -1417,6 +1443,56 @@ class Item
return $stored; return $stored;
} }
/**
* Returns the origin uid of a post if the given user is allowed to see it.
*
* @param int $uriid
* @param int $uid
* @return int
*/
private static function GetOriginUidForUriId(int $uriid, int $uid)
{
if (Post::exists(['uri-id' => $uriid, 'uid' => $uid])) {
return $uid;
}
$post = Post::selectFirst(['uid', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'private'], ['uri-id' => $uriid, 'origin' => true]);
if (!empty($post)) {
if (in_array($post['private'], [Item::PUBLIC, Item::UNLISTED])) {
return $post['uid'];
}
$pcid = Contact::getPublicIdByUserId($uid);
if (empty($pcid)) {
return null;
}
foreach (Item::enumeratePermissions($post, true) as $receiver) {
if ($receiver == $pcid) {
return $post['uid'];
}
}
return null;
}
if (Post::exists(['uri-id' => $uriid, 'uid' => 0])) {
return 0;
}
// When the post belongs to a a forum then all forum users are allowed to access it
foreach (Tag::getByURIId($uriid, [Tag::MENTION, Tag::EXCLUSIVE_MENTION]) as $tag) {
if (DBA::exists('contact', ['uid' => $uid, 'nurl' => Strings::normaliseLink($tag['url']), 'contact-type' => Contact::TYPE_COMMUNITY])) {
$target_uid = User::getIdForURL($tag['url']);
if (!empty($target_uid)) {
return $target_uid;
}
}
}
return null;
}
/** /**
* Store a public item array for the given users * Store a public item array for the given users
* *
@ -1443,6 +1519,7 @@ class Item
return 0; return 0;
} }
// Data from the "post-user" table
unset($item['id']); unset($item['id']);
unset($item['mention']); unset($item['mention']);
unset($item['starred']); unset($item['starred']);
@ -1451,11 +1528,14 @@ class Item
unset($item['pinned']); unset($item['pinned']);
unset($item['ignored']); unset($item['ignored']);
unset($item['pubmail']); unset($item['pubmail']);
unset($item['forum_mode']);
unset($item['event-id']); unset($item['event-id']);
unset($item['hidden']); unset($item['hidden']);
unset($item['notification-type']); unset($item['notification-type']);
unset($item['post-reason']);
// Data from the "post-delivery-data" table
unset($item['postopts']);
unset($item['inform']);
$item['uid'] = $uid; $item['uid'] = $uid;
$item['origin'] = 0; $item['origin'] = 0;
@ -1693,7 +1773,10 @@ class Item
} }
/** /**
* Creates an unique guid out of a given uri * Creates an unique guid out of a given uri.
* This function is used for messages outside the fediverse (Connector posts, feeds, Mails, ...)
* Posts that are created on this system are using System::createUUID.
* Received ActivityPub posts are using Processor::getGUIDByURL.
* *
* @param string $uri uri of an item entry * @param string $uri uri of an item entry
* @param string $host hostname for the GUID prefix * @param string $host hostname for the GUID prefix
@ -1705,19 +1788,14 @@ class Item
// We have to avoid that different routines could accidentally create the same value // We have to avoid that different routines could accidentally create the same value
$parsed = parse_url($uri); $parsed = parse_url($uri);
// We use a hash of the hostname as prefix for the guid
$guid_prefix = hash("crc32", $host);
// Remove the scheme to make sure that "https" and "http" doesn't make a difference // Remove the scheme to make sure that "https" and "http" doesn't make a difference
unset($parsed["scheme"]); unset($parsed["scheme"]);
// Glue it together to be able to make a hash from it // Glue it together to be able to make a hash from it
$host_id = implode("/", $parsed); $host_id = implode("/", $parsed);
// We could use any hash algorithm since it isn't a security issue // Use a mixture of several hashes to provide some GUID like experience
$host_hash = hash("ripemd128", $host_id); return hash("crc32", $host) . '-'. hash('joaat', $host_id) . '-'. hash('fnv164', $host_id);
return $guid_prefix.$host_hash;
} }
/** /**
@ -1875,7 +1953,7 @@ class Item
$owner = User::getOwnerDataById($uid); $owner = User::getOwnerDataById($uid);
if (!DBA::isResult($owner)) { if (!DBA::isResult($owner)) {
Logger::warning('User not found, quitting.', ['uid' => $uid]); Logger::warning('User not found, quitting here.', ['uid' => $uid]);
return false; return false;
} }
@ -1884,85 +1962,57 @@ class Item
return false; return false;
} }
$item = Post::selectFirst(self::ITEM_FIELDLIST, ['id' => $item_id]); $item = Post::selectFirst(self::ITEM_FIELDLIST, ['id' => $item_id, 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT], 'origin' => false]);
if (!DBA::isResult($item)) { if (!DBA::isResult($item)) {
Logger::warning('Post not found, quitting.', ['id' => $item_id]); Logger::debug('Post is an activity or origin or not found at all, quitting here.', ['id' => $item_id]);
return false; return false;
} }
if ($item['wall'] || $item['origin'] || ($item['gravity'] != GRAVITY_PARENT)) { if ($item['gravity'] == GRAVITY_PARENT) {
Logger::debug('Wall item, origin item or no parent post, quitting here.', ['wall' => $item['wall'], 'origin' => $item['origin'], 'gravity' => $item['gravity'], 'id' => $item_id, 'uri-id' => $item['uri-id'], 'guid' => $item['guid']]); $tags = Tag::getByURIId($item['uri-id'], [Tag::MENTION, Tag::EXCLUSIVE_MENTION]);
return false; foreach ($tags as $tag) {
} if (Strings::compareLink($owner['url'], $tag['url'])) {
$mention = true;
$tags = Tag::getByURIId($item['uri-id'], [Tag::MENTION, Tag::EXCLUSIVE_MENTION]); Logger::info('Mention found in tag.', ['url' => $tag['url'], 'uri' => $item['uri'], 'uid' => $uid, 'id' => $item_id, 'uri-id' => $item['uri-id'], 'guid' => $item['guid']]);
foreach ($tags as $tag) {
if (Strings::compareLink($owner['url'], $tag['url'])) {
$mention = true;
Logger::info('Mention found in tag.', ['url' => $tag['url'], 'uri' => $item['uri'], 'uid' => $uid, 'id' => $item_id, 'uri-id' => $item['uri-id'], 'guid' => $item['guid']]);
}
}
// This check can most likely be removed since we always are having the tags
if (!$mention) {
$cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism', $item['body'], $matches, PREG_SET_ORDER);
if ($cnt) {
foreach ($matches as $mtch) {
if (Strings::compareLink($owner['url'], $mtch[1])) {
$mention = true;
Logger::notice('Mention found in body.', ['mention' => $mtch[2], 'uri' => $item['uri'], 'uid' => $uid, 'id' => $item_id, 'uri-id' => $item['uri-id'], 'guid' => $item['guid']]);
}
} }
} }
if (!$mention) {
Logger::info('Top-level post without mention is deleted.', ['uri' => $item['uri'], $uid, 'id' => $item_id, 'uri-id' => $item['uri-id'], 'guid' => $item['guid']]);
Post\User::delete(['uri-id' => $item['uri-id'], 'uid' => $item['uid']]);
return true;
}
$arr = ['item' => $item, 'user' => $owner];
Hook::callAll('tagged', $arr);
} else {
$tags = Tag::getByURIId($item['parent-uri-id'], [Tag::MENTION, Tag::EXCLUSIVE_MENTION]);
foreach ($tags as $tag) {
if (Strings::compareLink($owner['url'], $tag['url'])) {
$mention = true;
Logger::info('Mention found in parent tag.', ['url' => $tag['url'], 'uri' => $item['uri'], 'uid' => $uid, 'id' => $item_id, 'uri-id' => $item['uri-id'], 'guid' => $item['guid']]);
}
}
if (!$mention) {
Logger::debug('No mentions found in parent, quitting here.', ['id' => $item_id, 'uri-id' => $item['uri-id'], 'guid' => $item['guid']]);
return false;
}
} }
if (!$mention) {
Logger::info('Top-level post without mention is deleted.', ['uri' => $item['uri'], $uid, 'id' => $item_id, 'uri-id' => $item['uri-id'], 'guid' => $item['guid']]);
Post\User::delete(['uri-id' => $item['uri-id'], 'uid' => $item['uid']]);
return true;
}
$arr = ['item' => $item, 'user' => $owner];
Hook::callAll('tagged', $arr);
Logger::info('Community post will be distributed', ['uri' => $item['uri'], 'uid' => $uid, 'id' => $item_id, 'uri-id' => $item['uri-id'], 'guid' => $item['guid']]); Logger::info('Community post will be distributed', ['uri' => $item['uri'], 'uid' => $uid, 'id' => $item_id, 'uri-id' => $item['uri-id'], 'guid' => $item['guid']]);
self::performActivity($item['id'], 'announce', $uid); if ($owner['page-flags'] == User::PAGE_FLAGS_PRVGROUP) {
$allow_cid = '';
/** $allow_gid = '<' . Group::FOLLOWERS . '>';
* All the following lines are only needed for private forums and compatibility to older systems without AP support. $deny_cid = '';
* A possible way would be that the followers list of a forum would always be readable by all followers. $deny_gid = '';
* So this would mean that the comment distribution could be done exactly for the intended audience. self::performActivity($item['id'], 'announce', $uid, $allow_cid, $allow_gid, $deny_cid, $deny_gid);
* Or possibly we could store the receivers that had been in the "announce" message above and use this.
*/
// also reset all the privacy bits to the forum default permissions
if ($owner['allow_cid'] || $owner['allow_gid'] || $owner['deny_cid'] || $owner['deny_gid']) {
$private = self::PRIVATE;
} elseif (DI::pConfig()->get($owner['uid'], 'system', 'unlisted')) {
$private = self::UNLISTED;
} else { } else {
$private = self::PUBLIC; self::performActivity($item['id'], 'announce', $uid);
} }
$permissionSet = DI::permissionSet()->selectOrCreate(
DI::permissionSetFactory()->createFromString(
$owner['uid'],
$owner['allow_cid'],
$owner['allow_gid'],
$owner['deny_cid'],
$owner['deny_gid']
));
$forum_mode = ($owner['page-flags'] == User::PAGE_FLAGS_PRVGROUP) ? 2 : 1;
$fields = ['wall' => true, 'origin' => true, 'forum_mode' => $forum_mode, 'contact-id' => $owner['id'],
'owner-id' => Contact::getPublicIdByUserId($uid), 'private' => $private, 'psid' => $permissionSet->id];
self::update($fields, ['id' => $item['id']]);
Worker::add(['priority' => PRIORITY_HIGH, 'dont_fork' => true], 'Notifier', Delivery::POST, (int)$item['uri-id'], (int)$item['uid']);
Logger::info('Community post had been distributed', ['uri' => $item['uri'], 'uid' => $uid, 'id' => $item_id, 'uri-id' => $item['uri-id'], 'guid' => $item['guid']]); Logger::info('Community post had been distributed', ['uri' => $item['uri'], 'uid' => $uid, 'id' => $item_id, 'uri-id' => $item['uri-id'], 'guid' => $item['guid']]);
return false; return false;
} }
@ -2325,12 +2375,17 @@ class Item
* *
* Toggle activities as like,dislike,attend of an item * Toggle activities as like,dislike,attend of an item
* *
* @param int $item_id * @param int $item_id
* @param string $verb * @param string $verb
* Activity verb. One of * Activity verb. One of
* like, unlike, dislike, undislike, attendyes, unattendyes, * like, unlike, dislike, undislike, attendyes, unattendyes,
* attendno, unattendno, attendmaybe, unattendmaybe, * attendno, unattendno, attendmaybe, unattendmaybe,
* announce, unannouce * announce, unannouce
* @param int $uid
* @param string $allow_cid
* @param string $allow_gid
* @param string $deny_cid
* @param string $deny_gid
* @return bool * @return bool
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException * @throws \ImagickException
@ -2338,7 +2393,7 @@ class Item
* array $arr * array $arr
* 'post_id' => ID of posted item * 'post_id' => ID of posted item
*/ */
public static function performActivity(int $item_id, string $verb, int $uid) public static function performActivity(int $item_id, string $verb, int $uid, string $allow_cid = null, string $allow_gid = null, string $deny_cid = null, string $deny_gid = null)
{ {
if (empty($uid)) { if (empty($uid)) {
return false; return false;
@ -2499,10 +2554,10 @@ class Item
'body' => $activity, 'body' => $activity,
'verb' => $activity, 'verb' => $activity,
'object-type' => $objtype, 'object-type' => $objtype,
'allow_cid' => $item['allow_cid'], 'allow_cid' => $allow_cid ?? $item['allow_cid'],
'allow_gid' => $item['allow_gid'], 'allow_gid' => $allow_gid ?? $item['allow_gid'],
'deny_cid' => $item['deny_cid'], 'deny_cid' => $deny_cid ?? $item['deny_cid'],
'deny_gid' => $item['deny_gid'], 'deny_gid' => $deny_gid ?? $item['deny_gid'],
'visible' => 1, 'visible' => 1,
'unseen' => 1, 'unseen' => 1,
]; ];
@ -3163,30 +3218,20 @@ class Item
} }
/** /**
* Is the given item array a post that is sent as starting post to a forum? * Does the given uri-id belongs to a post that is sent as starting post to a forum?
* *
* @param array $item * @param int $uri_id
* @param array $owner
* *
* @return boolean "true" when it is a forum post * @return boolean "true" when it is a forum post
*/ */
public static function isForumPost(array $item, array $owner = []) public static function isForumPost(int $uri_id)
{ {
if (empty($owner)) { foreach (Tag::getByURIId($uri_id, [Tag::EXCLUSIVE_MENTION]) as $tag) {
$owner = User::getOwnerDataById($item['uid']); if (DBA::exists('contact', ['uid' => 0, 'nurl' => Strings::normaliseLink($tag['url']), 'contact-type' => Contact::TYPE_COMMUNITY])) {
if (empty($owner)) { return true;
return false;
} }
} }
return false;
if (($item['author-id'] == $item['owner-id']) ||
($owner['id'] == $item['contact-id']) ||
($item['uri-id'] != $item['parent-uri-id']) ||
$item['origin']) {
return false;
}
return Contact::isForum($item['contact-id']);
} }
/** /**

View File

@ -21,6 +21,7 @@
namespace Friendica\Model; namespace Friendica\Model;
use Friendica\Core\ACL;
use Friendica\Core\Logger; use Friendica\Core\Logger;
use Friendica\Core\System; use Friendica\Core\System;
use Friendica\Core\Worker; use Friendica\Core\Worker;
@ -39,10 +40,12 @@ class Mail
* Insert private message * Insert private message
* *
* @param array $msg * @param array $msg
* @param bool $notifiction * @param bool $notification
* @return int|boolean Message ID or false on error * @return int|boolean Message ID or false on error
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/ */
public static function insert($msg, $notifiction = true) public static function insert($msg, $notification = true)
{ {
if (!isset($msg['reply'])) { if (!isset($msg['reply'])) {
$msg['reply'] = DBA::exists('mail', ['parent-uri' => $msg['parent-uri']]); $msg['reply'] = DBA::exists('mail', ['parent-uri' => $msg['parent-uri']]);
@ -92,7 +95,7 @@ class Mail
DBA::update('conv', ['updated' => DateTimeFormat::utcNow()], ['id' => $msg['convid']]); DBA::update('conv', ['updated' => DateTimeFormat::utcNow()], ['id' => $msg['convid']]);
} }
if ($notifiction) { if ($notification) {
$user = User::getById($msg['uid']); $user = User::getById($msg['uid']);
// send notifications. // send notifications.
$notif_params = [ $notif_params = [
@ -139,11 +142,15 @@ class Mail
return -2; return -2;
} }
$contact = DBA::selectFirst('contact', [], ['id' => $recipient, 'uid' => local_user()]); $contacts = ACL::getValidMessageRecipientsForUser(local_user());
if (!DBA::isResult($contact)) {
$contactIndex = array_search($recipient, array_column($contacts, 'id'));
if ($contactIndex === false) {
return -2; return -2;
} }
$contact = $contacts[$contactIndex];
Photo::setPermissionFromBody($body, local_user(), $me['id'], '<' . $contact['id'] . '>', '', '', ''); Photo::setPermissionFromBody($body, local_user(), $me['id'], '<' . $contact['id'] . '>', '', '', '');
$guid = System::createUUID(); $guid = System::createUUID();
@ -167,20 +174,12 @@ class Mail
$convuri = ''; $convuri = '';
if (!$convid) { if (!$convid) {
// create a new conversation // create a new conversation
$recip_host = substr($contact['url'], strpos($contact['url'], '://') + 3);
$recip_host = substr($recip_host, 0, strpos($recip_host, '/'));
$recip_handle = (($contact['addr']) ? $contact['addr'] : $contact['nick'] . '@' . $recip_host);
$sender_handle = $a->getLoggedInUserNickname() . '@' . substr(DI::baseUrl(), strpos(DI::baseUrl(), '://') + 3);
$conv_guid = System::createUUID(); $conv_guid = System::createUUID();
$convuri = $recip_handle . ':' . $conv_guid; $convuri = $contact['addr'] . ':' . $conv_guid;
$handles = $recip_handle . ';' . $sender_handle; $fields = ['uid' => local_user(), 'guid' => $conv_guid, 'creator' => $me['addr'],
$fields = ['uid' => local_user(), 'guid' => $conv_guid, 'creator' => $sender_handle,
'created' => DateTimeFormat::utcNow(), 'updated' => DateTimeFormat::utcNow(), 'created' => DateTimeFormat::utcNow(), 'updated' => DateTimeFormat::utcNow(),
'subject' => $subject, 'recips' => $handles]; 'subject' => $subject, 'recips' => $contact['addr'] . ';' . $me['addr']];
if (DBA::insert('conv', $fields)) { if (DBA::insert('conv', $fields)) {
$convid = DBA::lastInsertId(); $convid = DBA::lastInsertId();
} }

View File

@ -518,7 +518,7 @@ class Media
$condition = DBA::mergeConditions($condition, ['type' => $types]); $condition = DBA::mergeConditions($condition, ['type' => $types]);
} }
return DBA::selectToArray('post-media', [], $condition); return DBA::selectToArray('post-media', [], $condition, ['order' => ['id']]);
} }
/** /**

View File

@ -33,6 +33,7 @@ use Friendica\Model\Contact;
use Friendica\Model\Post; use Friendica\Model\Post;
use Friendica\Model\Subscription; use Friendica\Model\Subscription;
use Friendica\Model\Tag; use Friendica\Model\Tag;
use Friendica\Model\User;
use Friendica\Navigation\Notifications; use Friendica\Navigation\Notifications;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
use Friendica\Protocol\Activity; use Friendica\Protocol\Activity;
@ -176,12 +177,24 @@ class UserNotification
return; return;
} }
$user = User::getById($uid, ['account-type']);
if (in_array($user['account-type'], [User::ACCOUNT_TYPE_COMMUNITY, User::ACCOUNT_TYPE_RELAY])) {
return;
}
$author = Contact::getById($item['author-id'], ['contact-type']);
if (empty($author)) {
return;
}
$notification_type = self::TYPE_NONE; $notification_type = self::TYPE_NONE;
if (self::checkShared($item, $uid)) { if (self::checkShared($item, $uid)) {
$notification_type = $notification_type | self::TYPE_SHARED; $notification_type = $notification_type | self::TYPE_SHARED;
self::insertNotificationByItem(self::TYPE_SHARED, $uid, $item); self::insertNotificationByItem(self::TYPE_SHARED, $uid, $item);
$notified = true; $notified = true;
} elseif ($author['contact-type'] == Contact::TYPE_COMMUNITY) {
return;
} else { } else {
$notified = false; $notified = false;
} }
@ -189,11 +202,16 @@ class UserNotification
$profiles = self::getProfileForUser($uid); $profiles = self::getProfileForUser($uid);
// Fetch all contacts for the given profiles // Fetch all contacts for the given profiles
$contacts = []; $contacts = [];
$iscommunity = false;
$ret = DBA::select('contact', ['id'], ['uid' => 0, 'nurl' => $profiles]); $ret = DBA::select('contact', ['id', 'contact-type'], ['uid' => 0, 'nurl' => $profiles]);
while ($contact = DBA::fetch($ret)) { while ($contact = DBA::fetch($ret)) {
$contacts[] = $contact['id']; $contacts[] = $contact['id'];
if ($contact['contact-type'] == Contact::TYPE_COMMUNITY) {
$iscommunity = true;
}
} }
DBA::close($ret); DBA::close($ret);
@ -226,7 +244,7 @@ class UserNotification
} }
} }
if (self::checkDirectCommentedThread($item, $contacts)) { if (!$iscommunity && self::checkDirectCommentedThread($item, $contacts)) {
$notification_type = $notification_type | self::TYPE_DIRECT_THREAD_COMMENT; $notification_type = $notification_type | self::TYPE_DIRECT_THREAD_COMMENT;
if (!$notified) { if (!$notified) {
self::insertNotificationByItem(self::TYPE_DIRECT_THREAD_COMMENT, $uid, $item); self::insertNotificationByItem(self::TYPE_DIRECT_THREAD_COMMENT, $uid, $item);
@ -290,7 +308,7 @@ class UserNotification
return; return;
} }
$notification = (new Notifications\Factory\Notification(DI::logger()))->createForUser( $notification = (new Notifications\Factory\Notification(DI::baseUrl(), DI::l10n(), DI::localRelationship(), DI::logger()))->createForUser(
$uid, $uid,
$item['vid'], $item['vid'],
$type, $type,
@ -310,7 +328,7 @@ class UserNotification
/** /**
* Add a notification entry * Add a notification entry
* *
* @param int $actor Contact ID of the actor * @param int $actor Public contact ID of the actor
* @param string $verb One of the Activity verb constant values * @param string $verb One of the Activity verb constant values
* @param int $uid User ID * @param int $uid User ID
* @return boolean * @return boolean
@ -318,7 +336,7 @@ class UserNotification
*/ */
public static function insertNotification(int $actor, string $verb, int $uid): bool public static function insertNotification(int $actor, string $verb, int $uid): bool
{ {
$notification = (new Notifications\Factory\Notification(DI::logger()))->createForRelationship( $notification = (new Notifications\Factory\Notification(DI::baseUrl(), DI::l10n(), DI::localRelationship(), DI::logger()))->createForRelationship(
$uid, $uid,
$actor, $actor,
$verb $verb
@ -401,6 +419,14 @@ class UserNotification
return false; return false;
} }
// Don't notify about reshares by communities of our own posts or each time someone comments
if (($item['verb'] == Activity::ANNOUNCE) && DBA::exists('contact', ['id' => $item['contact-id'], 'contact-type' => Contact::TYPE_COMMUNITY])) {
$post = Post::selectFirst(['origin', 'gravity'], ['uri-id' => $item['thr-parent-id'], 'uid' => $uid]);
if ($post['origin'] || ($post['gravity'] != GRAVITY_PARENT)) {
return false;
}
}
// Check if the contact posted or shared something directly // Check if the contact posted or shared something directly
if (DBA::exists('contact', ['id' => $item['contact-id'], 'notify_new_posts' => true])) { if (DBA::exists('contact', ['id' => $item['contact-id'], 'notify_new_posts' => true])) {
return true; return true;

View File

@ -362,7 +362,7 @@ class Profile
} }
// Fetch the account type // Fetch the account type
$account_type = Contact::getAccountType($profile); $account_type = Contact::getAccountType($profile['account-type']);
if (!empty($profile['address']) || !empty($profile['location'])) { if (!empty($profile['address']) || !empty($profile['location'])) {
$location = DI::l10n()->t('Location:'); $location = DI::l10n()->t('Location:');

View File

@ -48,15 +48,20 @@ class Tag
*/ */
const IMPLICIT_MENTION = 8; const IMPLICIT_MENTION = 8;
/** /**
* An exclusive mention transfers the ownership of the post to the target account, usually a forum. * An exclusive mention transmits the post only to the target account without transmitting it to the followers, usually a forum.
*/ */
const EXCLUSIVE_MENTION = 9; const EXCLUSIVE_MENTION = 9;
const TO = 10;
const CC = 11;
const BTO = 12;
const BCC = 13;
const TAG_CHARACTER = [ const TAG_CHARACTER = [
self::HASHTAG => '#', self::HASHTAG => '#',
self::MENTION => '@', self::MENTION => '@',
self::IMPLICIT_MENTION => '%',
self::EXCLUSIVE_MENTION => '!', self::EXCLUSIVE_MENTION => '!',
self::IMPLICIT_MENTION => '%',
]; ];
/** /**
@ -66,9 +71,8 @@ class Tag
* @param integer $type * @param integer $type
* @param string $name * @param string $name
* @param string $url * @param string $url
* @param boolean $probing
*/ */
public static function store(int $uriid, int $type, string $name, string $url = '', $probing = true) public static function store(int $uriid, int $type, string $name, string $url = '')
{ {
if ($type == self::HASHTAG) { if ($type == self::HASHTAG) {
// Trim Unicode non-word characters // Trim Unicode non-word characters
@ -77,7 +81,7 @@ class Tag
$tags = explode(self::TAG_CHARACTER[self::HASHTAG], $name); $tags = explode(self::TAG_CHARACTER[self::HASHTAG], $name);
if (count($tags) > 1) { if (count($tags) > 1) {
foreach ($tags as $tag) { foreach ($tags as $tag) {
self::store($uriid, $type, $tag, $url, $probing); self::store($uriid, $type, $tag, $url);
} }
return; return;
} }
@ -90,7 +94,7 @@ class Tag
$cid = 0; $cid = 0;
$tagid = 0; $tagid = 0;
if (in_array($type, [self::MENTION, self::EXCLUSIVE_MENTION, self::IMPLICIT_MENTION])) { if (in_array($type, [self::MENTION, self::EXCLUSIVE_MENTION, self::IMPLICIT_MENTION, self::TO, self::CC, self::BTO, self::BCC])) {
if (empty($url)) { if (empty($url)) {
// No mention without a contact url // No mention without a contact url
return; return;
@ -100,32 +104,13 @@ class Tag
Logger::notice('Wrong scheme in url', ['url' => $url, 'callstack' => System::callstack(20)]); Logger::notice('Wrong scheme in url', ['url' => $url, 'callstack' => System::callstack(20)]);
} }
if (!$probing) { $cid = Contact::getIdForURL($url, 0, false);
$condition = ['nurl' => Strings::normaliseLink($url), 'uid' => 0, 'deleted' => false]; Logger::debug('Got id for contact', ['cid' => $cid, 'url' => $url]);
$contact = DBA::selectFirst('contact', ['id'], $condition, ['order' => ['id']]);
if (DBA::isResult($contact)) {
$cid = $contact['id'];
Logger::info('Got id for contact url', ['cid' => $cid, 'url' => $url]);
}
if (empty($cid)) {
$ssl_url = str_replace('http://', 'https://', $url);
$condition = ['`alias` IN (?, ?, ?) AND `uid` = ? AND NOT `deleted`', $url, Strings::normaliseLink($url), $ssl_url, 0];
$contact = DBA::selectFirst('contact', ['id'], $condition, ['order' => ['id']]);
if (DBA::isResult($contact)) {
$cid = $contact['id'];
Logger::info('Got id for contact alias', ['cid' => $cid, 'url' => $url]);
}
}
} else {
$cid = Contact::getIdForURL($url, 0, false);
Logger::info('Got id by probing', ['cid' => $cid, 'url' => $url]);
}
if (empty($cid)) { if (empty($cid)) {
// The contact wasn't found in the system (most likely some dead account) // The contact wasn't found in the system (most likely some dead account)
// We ensure that we only store a single entry by overwriting the previous name // We ensure that we only store a single entry by overwriting the previous name
Logger::info('Contact not found, updating tag', ['url' => $url, 'name' => $name]); Logger::info('URL is not a known contact, updating tag', ['url' => $url, 'name' => $name]);
if (!DBA::exists('tag', ['name' => substr($name, 0, 96), 'url' => $url])) { if (!DBA::exists('tag', ['name' => substr($name, 0, 96), 'url' => $url])) {
DBA::update('tag', ['name' => substr($name, 0, 96)], ['url' => $url]); DBA::update('tag', ['name' => substr($name, 0, 96)], ['url' => $url]);
} }
@ -133,10 +118,12 @@ class Tag
} }
if (empty($cid)) { if (empty($cid)) {
if (($type != self::HASHTAG) && !empty($url) && ($url != $name)) { if (!in_array($type, [self::TO, self::CC, self::BTO, self::BCC])) {
$url = strtolower($url); if (($type != self::HASHTAG) && !empty($url) && ($url != $name)) {
} else { $url = strtolower($url);
$url = ''; } else {
$url = '';
}
} }
$tagid = self::getID($name, $url); $tagid = self::getID($name, $url);
@ -286,7 +273,7 @@ class Tag
*/ */
public static function existsForPost(int $uriid) public static function existsForPost(int $uriid)
{ {
return DBA::exists('post-tag', ['uri-id' => $uriid, 'type' => [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION, self::EXCLUSIVE_MENTION]]); return DBA::exists('post-tag', ['uri-id' => $uriid, 'type' => [self::HASHTAG, self::MENTION, self::EXCLUSIVE_MENTION, self::IMPLICIT_MENTION]]);
} }
/** /**
@ -368,7 +355,7 @@ class Tag
return; return;
} }
$tags = DBA::select('tag-view', ['name', 'url'], ['uri-id' => $parent_uri_id]); $tags = DBA::select('tag-view', ['name', 'url'], ['uri-id' => $parent_uri_id, 'type' => [self::MENTION, self::EXCLUSIVE_MENTION, self::IMPLICIT_MENTION]]);
while ($tag = DBA::fetch($tags)) { while ($tag = DBA::fetch($tags)) {
self::store($uri_id, self::IMPLICIT_MENTION, $tag['name'], $tag['url']); self::store($uri_id, self::IMPLICIT_MENTION, $tag['name'], $tag['url']);
} }
@ -383,7 +370,7 @@ class Tag
* @return array * @return array
* @throws \Exception * @throws \Exception
*/ */
public static function getByURIId(int $uri_id, array $type = [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION, self::EXCLUSIVE_MENTION]) public static function getByURIId(int $uri_id, array $type = [self::HASHTAG, self::MENTION, self::EXCLUSIVE_MENTION, self::IMPLICIT_MENTION])
{ {
$condition = ['uri-id' => $uri_id, 'type' => $type]; $condition = ['uri-id' => $uri_id, 'type' => $type];
return DBA::selectToArray('tag-view', ['type', 'name', 'url'], $condition); return DBA::selectToArray('tag-view', ['type', 'name', 'url'], $condition);
@ -397,7 +384,7 @@ class Tag
* @return string tags and mentions * @return string tags and mentions
* @throws \Exception * @throws \Exception
*/ */
public static function getCSVByURIId(int $uri_id, array $type = [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION, self::EXCLUSIVE_MENTION]) public static function getCSVByURIId(int $uri_id, array $type = [self::HASHTAG, self::MENTION, self::EXCLUSIVE_MENTION, self::IMPLICIT_MENTION])
{ {
$tag_list = []; $tag_list = [];
$tags = self::getByURIId($uri_id, $type); $tags = self::getByURIId($uri_id, $type);

View File

@ -25,6 +25,7 @@ use Friendica\BaseModule;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Friendica\Model\User; use Friendica\Model\User;
use Friendica\Protocol\ActivityPub; use Friendica\Protocol\ActivityPub;
use Friendica\Util\HTTPSignature;
/** /**
* ActivityPub Followers * ActivityPub Followers
@ -45,7 +46,7 @@ class Followers extends BaseModule
$page = $_REQUEST['page'] ?? null; $page = $_REQUEST['page'] ?? null;
$followers = ActivityPub\Transmitter::getContacts($owner, [Contact::FOLLOWER, Contact::FRIEND], 'followers', $page); $followers = ActivityPub\Transmitter::getContacts($owner, [Contact::FOLLOWER, Contact::FRIEND], 'followers', $page, (string)HTTPSignature::getSigner('', $_SERVER));
header('Content-Type: application/activity+json'); header('Content-Type: application/activity+json');
echo json_encode($followers); echo json_encode($followers);

View File

@ -70,9 +70,7 @@ class Objects extends BaseModule
} }
} }
$item = Post::selectFirst(['id', 'uid', 'origin', 'author-link', 'changed', 'private', 'psid', 'gravity', 'deleted', 'parent-uri-id'], $item = Post::selectFirst([], ['uri-id' => $itemuri['id'], 'origin' => true]);
['uri-id' => $itemuri['id']], ['order' => ['origin' => true]]);
if (!DBA::isResult($item)) { if (!DBA::isResult($item)) {
throw new HTTPException\NotFoundException(); throw new HTTPException\NotFoundException();
} }
@ -81,25 +79,17 @@ class Objects extends BaseModule
if (!$validated) { if (!$validated) {
$requester = HTTPSignature::getSigner('', $_SERVER); $requester = HTTPSignature::getSigner('', $_SERVER);
if (!empty($requester) && $item['origin']) { if (!empty($requester)) {
$requester_id = Contact::getIdForURL($requester, $item['uid']); $receivers = Item::enumeratePermissions($item, false);
if (!empty($requester_id)) { $receivers[] = $item['contact-id'];
$permissionSets = DI::permissionSet()->selectByContactId($requester_id, $item['uid']);
$psids = array_merge($permissionSets->column('id'), [PermissionSet::PUBLIC]); $validated = in_array(Contact::getIdForURL($requester, $item['uid']), $receivers);
$validated = in_array($item['psid'], $psids); if (!$validated) {
$validated = in_array(Contact::getIdForURL($requester), $receivers);
} }
} }
} }
if ($validated) {
// Valid items are original post or posted from this node (including in the case of a forum)
$validated = ($item['origin'] || (parse_url($item['author-link'], PHP_URL_HOST) == parse_url(DI::baseUrl()->get(), PHP_URL_HOST)));
if (!$validated && $item['deleted']) {
$validated = Post::exists(['origin' => true, 'uri-id' => $item['parent-uri-id']]);
}
}
if (!$validated) { if (!$validated) {
throw new HTTPException\NotFoundException(); throw new HTTPException\NotFoundException();
} }

View File

@ -164,19 +164,19 @@ class Federation extends BaseAdmin
} }
$gserver['platform'] = $systems[$platform]['name']; $gserver['platform'] = $systems[$platform]['name'];
$gserver['totallbl'] = DI::l10n()->t('%d total systems', $gserver['total']); $gserver['totallbl'] = DI::l10n()->t('%s total systems', number_format($gserver['total']));
$gserver['monthlbl'] = DI::l10n()->t('%d active users last month', $gserver['month']); $gserver['monthlbl'] = DI::l10n()->t('%s active users last month', number_format($gserver['month']));
$gserver['halfyearlbl'] = DI::l10n()->t('%d active users last six months', $gserver['halfyear']); $gserver['halfyearlbl'] = DI::l10n()->t('%s active users last six months', number_format($gserver['halfyear']));
$gserver['userslbl'] = DI::l10n()->t('%d registered users', $gserver['users']); $gserver['userslbl'] = DI::l10n()->t('%s registered users', number_format($gserver['users']));
$gserver['postslbl'] = DI::l10n()->t('%d locally created posts and comments', $gserver['posts']); $gserver['postslbl'] = DI::l10n()->t('%s locally created posts and comments', number_format($gserver['posts']));
if (($gserver['users'] > 0) && ($gserver['posts'] > 0)) { if (($gserver['users'] > 0) && ($gserver['posts'] > 0)) {
$gserver['postsuserlbl'] = DI::l10n()->t('%d posts per user', $gserver['posts'] / $gserver['users']); $gserver['postsuserlbl'] = DI::l10n()->t('%s posts per user', number_format($gserver['posts'] / $gserver['users'], 1));
} else { } else {
$gserver['postsuserlbl'] = ''; $gserver['postsuserlbl'] = '';
} }
if (($gserver['users'] > 0) && ($gserver['total'] > 0)) { if (($gserver['users'] > 0) && ($gserver['total'] > 0)) {
$gserver['userssystemlbl'] = DI::l10n()->t('%d users per system', $gserver['users'] / $gserver['total']); $gserver['userssystemlbl'] = DI::l10n()->t('%s users per system', number_format($gserver['users'] / $gserver['total'], 1));
} else { } else {
$gserver['userssystemlbl'] = ''; $gserver['userssystemlbl'] = '';
} }
@ -196,7 +196,7 @@ class Federation extends BaseAdmin
'$intro' => $intro, '$intro' => $intro,
'$counts' => $counts, '$counts' => $counts,
'$version' => FRIENDICA_VERSION, '$version' => FRIENDICA_VERSION,
'$legendtext' => DI::l10n()->t('Currently this node is aware of %d nodes (%d active users last month, %d active users last six months, %d registered users in total) from the following platforms:', $total, $month, $halfyear, $users), '$legendtext' => DI::l10n()->t('Currently this node is aware of %d nodes (%d active users last month, %d active users last six months, %d registered users in total) from the following platforms:', number_format($total), number_format($month), number_format($halfyear), number_format($users)),
]); ]);
} }

View File

@ -21,9 +21,9 @@
namespace Friendica\Module\Admin\Logs; namespace Friendica\Module\Admin\Logs;
use Friendica\DI;
use Friendica\Core\Renderer; use Friendica\Core\Renderer;
use Friendica\Core\Theme; use Friendica\Core\Theme;
use Friendica\DI;
use Friendica\Module\BaseAdmin; use Friendica\Module\BaseAdmin;
use Psr\Log\LogLevel; use Psr\Log\LogLevel;
@ -80,9 +80,10 @@ class View extends BaseAdmin
} }
} }
return Renderer::replaceMacros($t, [ return Renderer::replaceMacros($t, [
'$title' => DI::l10n()->t('Administration'), '$baseurl' => DI::baseUrl()->get(true),
'$page' => DI::l10n()->t('View Logs'), '$title' => DI::l10n()->t('Administration'),
'$l10n' => [ '$page' => DI::l10n()->t('View Logs'),
'$l10n' => [
'Search' => DI::l10n()->t('Search'), 'Search' => DI::l10n()->t('Search'),
'Search_in_logs' => DI::l10n()->t('Search in logs'), 'Search_in_logs' => DI::l10n()->t('Search in logs'),
'Show_all' => DI::l10n()->t('Show all'), 'Show_all' => DI::l10n()->t('Show all'),

View File

@ -526,7 +526,7 @@ class Site extends BaseAdmin
'$touch_icon' => ['touch_icon', DI::l10n()->t('Touch icon'), DI::config()->get('system', 'touch_icon'), DI::l10n()->t('Link to an icon that will be used for tablets and mobiles.')], '$touch_icon' => ['touch_icon', DI::l10n()->t('Touch icon'), DI::config()->get('system', 'touch_icon'), DI::l10n()->t('Link to an icon that will be used for tablets and mobiles.')],
'$additional_info' => ['additional_info', DI::l10n()->t('Additional Info'), $additional_info, DI::l10n()->t('For public servers: you can add additional information here that will be listed at %s/servers.', Search::getGlobalDirectory())], '$additional_info' => ['additional_info', DI::l10n()->t('Additional Info'), $additional_info, DI::l10n()->t('For public servers: you can add additional information here that will be listed at %s/servers.', Search::getGlobalDirectory())],
'$language' => ['language', DI::l10n()->t('System language'), DI::config()->get('system', 'language'), '', $lang_choices], '$language' => ['language', DI::l10n()->t('System language'), DI::config()->get('system', 'language'), '', $lang_choices],
'$theme' => ['theme', DI::l10n()->t('System theme'), DI::config()->get('system', 'theme'), DI::l10n()->t('Default system theme - may be over-ridden by user profiles - <a href="/admin/themes" id="cnftheme">Change default theme settings</a>'), $theme_choices], '$theme' => ['theme', DI::l10n()->t('System theme'), DI::config()->get('system', 'theme'), DI::l10n()->t('Default system theme - may be over-ridden by user profiles - <a href="%s" id="cnftheme">Change default theme settings</a>', DI::baseUrl()->get(true) . '/admin/themes'), $theme_choices],
'$theme_mobile' => ['theme_mobile', DI::l10n()->t('Mobile system theme'), DI::config()->get('system', 'mobile-theme', '---'), DI::l10n()->t('Theme for mobile devices'), $theme_choices_mobile], '$theme_mobile' => ['theme_mobile', DI::l10n()->t('Mobile system theme'), DI::config()->get('system', 'mobile-theme', '---'), DI::l10n()->t('Theme for mobile devices'), $theme_choices_mobile],
'$ssl_policy' => ['ssl_policy', DI::l10n()->t('SSL link policy'), DI::config()->get('system', 'ssl_policy'), DI::l10n()->t('Determines whether generated links should be forced to use SSL'), $ssl_choices], '$ssl_policy' => ['ssl_policy', DI::l10n()->t('SSL link policy'), DI::config()->get('system', 'ssl_policy'), DI::l10n()->t('Determines whether generated links should be forced to use SSL'), $ssl_choices],
'$force_ssl' => ['force_ssl', DI::l10n()->t('Force SSL'), DI::config()->get('system', 'force_ssl'), DI::l10n()->t('Force all Non-SSL requests to SSL - Attention: on some systems it could lead to endless loops.')], '$force_ssl' => ['force_ssl', DI::l10n()->t('Force SSL'), DI::config()->get('system', 'force_ssl'), DI::l10n()->t('Force all Non-SSL requests to SSL - Attention: on some systems it could lead to endless loops.')],
@ -570,8 +570,8 @@ class Site extends BaseAdmin
'$diaspora_not_able' => DI::l10n()->t('Diaspora support can\'t be enabled because Friendica was installed into a sub directory.'), '$diaspora_not_able' => DI::l10n()->t('Diaspora support can\'t be enabled because Friendica was installed into a sub directory.'),
'$diaspora_enabled' => ['diaspora_enabled', DI::l10n()->t('Enable Diaspora support'), DI::config()->get('system', 'diaspora_enabled', $diaspora_able), DI::l10n()->t('Enable built-in Diaspora network compatibility for communicating with diaspora servers.')], '$diaspora_enabled' => ['diaspora_enabled', DI::l10n()->t('Enable Diaspora support'), DI::config()->get('system', 'diaspora_enabled', $diaspora_able), DI::l10n()->t('Enable built-in Diaspora network compatibility for communicating with diaspora servers.')],
'$verifyssl' => ['verifyssl', DI::l10n()->t('Verify SSL'), DI::config()->get('system', 'verifyssl'), DI::l10n()->t('If you wish, you can turn on strict certificate checking. This will mean you cannot connect (at all) to self-signed SSL sites.')], '$verifyssl' => ['verifyssl', DI::l10n()->t('Verify SSL'), DI::config()->get('system', 'verifyssl'), DI::l10n()->t('If you wish, you can turn on strict certificate checking. This will mean you cannot connect (at all) to self-signed SSL sites.')],
'$proxyuser' => ['proxyuser', DI::l10n()->t('Proxy user'), DI::config()->get('system', 'proxyuser'), ''], '$proxyuser' => ['proxyuser', DI::l10n()->t('Proxy user'), DI::config()->get('system', 'proxyuser'), DI::l10n()->t('User name for the proxy server.')],
'$proxy' => ['proxy', DI::l10n()->t('Proxy URL'), DI::config()->get('system', 'proxy'), ''], '$proxy' => ['proxy', DI::l10n()->t('Proxy URL'), DI::config()->get('system', 'proxy'), DI::l10n()->t('If you want to use a proxy server that Friendica should use to connect to the network, put the URL of the proxy here.')],
'$timeout' => ['timeout', DI::l10n()->t('Network timeout'), DI::config()->get('system', 'curl_timeout'), DI::l10n()->t('Value is in seconds. Set to 0 for unlimited (not recommended).')], '$timeout' => ['timeout', DI::l10n()->t('Network timeout'), DI::config()->get('system', 'curl_timeout'), DI::l10n()->t('Value is in seconds. Set to 0 for unlimited (not recommended).')],
'$maxloadavg' => ['maxloadavg', DI::l10n()->t('Maximum Load Average'), DI::config()->get('system', 'maxloadavg'), DI::l10n()->t('Maximum system load before delivery and poll processes are deferred - default %d.', 20)], '$maxloadavg' => ['maxloadavg', DI::l10n()->t('Maximum Load Average'), DI::config()->get('system', 'maxloadavg'), DI::l10n()->t('Maximum system load before delivery and poll processes are deferred - default %d.', 20)],
'$min_memory' => ['min_memory', DI::l10n()->t('Minimal Memory'), DI::config()->get('system', 'min_memory'), DI::l10n()->t('Minimal free memory in MB for the worker. Needs access to /proc/meminfo - default 0 (deactivated).')], '$min_memory' => ['min_memory', DI::l10n()->t('Minimal Memory'), DI::config()->get('system', 'min_memory'), DI::l10n()->t('Minimal free memory in MB for the worker. Needs access to /proc/meminfo - default 0 (deactivated).')],

View File

@ -76,7 +76,7 @@ class Details extends BaseAdmin
require_once "view/theme/$theme/config.php"; require_once "view/theme/$theme/config.php";
if (function_exists('theme_admin')) { if (function_exists('theme_admin')) {
$admin_form = '<iframe onload="resizeIframe(this);" src="/admin/themes/' . $theme . '/embed?mode=minimal" width="100%" height="600px" frameborder="no"></iframe>'; $admin_form = '<iframe onload="resizeIframe(this);" src="' . DI::baseUrl()->get(true) . '/admin/themes/' . $theme . '/embed?mode=minimal" width="100%" height="600px" frameborder="no"></iframe>';
} }
} }

View File

@ -24,6 +24,7 @@ namespace Friendica\Module\Admin\Themes;
use Friendica\App; use Friendica\App;
use Friendica\Core\L10n; use Friendica\Core\L10n;
use Friendica\Core\Renderer; use Friendica\Core\Renderer;
use Friendica\DI;
use Friendica\Module\BaseAdmin; use Friendica\Module\BaseAdmin;
use Friendica\Module\Response; use Friendica\Module\Response;
use Friendica\Util\Profiler; use Friendica\Util\Profiler;
@ -94,7 +95,7 @@ class Embed extends BaseAdmin
$t = Renderer::getMarkupTemplate('admin/addons/embed.tpl'); $t = Renderer::getMarkupTemplate('admin/addons/embed.tpl');
return Renderer::replaceMacros($t, [ return Renderer::replaceMacros($t, [
'$action' => '/admin/themes/' . $theme . '/embed?mode=minimal', '$action' => DI::baseUrl()->get(true) . '/admin/themes/' . $theme . '/embed?mode=minimal',
'$form' => $admin_form, '$form' => $admin_form,
'$form_security_token' => self::getFormSecurityToken("admin_theme_settings"), '$form_security_token' => self::getFormSecurityToken("admin_theme_settings"),
]); ]);

View File

@ -37,7 +37,7 @@ class Index extends BaseAdmin
// reload active themes // reload active themes
if (!empty($_GET['action'])) { if (!empty($_GET['action'])) {
self::checkFormSecurityTokenRedirectOnError(DI::baseUrl()->get() . '/admin/themes', 'admin_themes', 't'); self::checkFormSecurityTokenRedirectOnError('/admin/themes', 'admin_themes', 't');
switch ($_GET['action']) { switch ($_GET['action']) {
case 'reload': case 'reload':

View File

@ -23,7 +23,9 @@ namespace Friendica\Module\Api\Friendica;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\Item; use Friendica\Model\Item;
use Friendica\Model\Post;
use Friendica\Module\BaseApi; use Friendica\Module\BaseApi;
use Friendica\Network\HTTPException\BadRequestException;
/** /**
* API endpoints: * API endpoints:
@ -49,15 +51,16 @@ class Activity extends BaseApi
'id' => 0, // Id of the post 'id' => 0, // Id of the post
], $request); ], $request);
$res = Item::performActivity($request['id'], $this->parameters['verb'], $uid); $post = Post::selectFirst(['id'], ['uri-id' => $request['id'], 'uid' => [0, $uid]], ['order' => ['uid' => true]]);
if (empty($post['id'])) {
throw new BadRequestException('Item id not found');
}
$res = Item::performActivity($post['id'], $this->parameters['verb'], $uid);
if ($res) { if ($res) {
if (($this->parameters['extension'] ?? '') == 'xml') { $status_info = DI::twitterStatus()->createFromUriId($request['id'], $uid)->toArray();
$ok = 'true'; $this->response->exit('status', ['status' => $status_info], $this->parameters['extension'] ?? null);
} else {
$ok = 'ok';
}
$this->response->exit('ok', ['ok' => $ok], $this->parameters['extension'] ?? null);
} else { } else {
$this->response->error(500, 'Error adding activity', '', $this->parameters['extension'] ?? null); $this->response->error(500, 'Error adding activity', '', $this->parameters['extension'] ?? null);
} }

View File

@ -23,7 +23,6 @@ namespace Friendica\Module\Api\Friendica\Events;
use Friendica\Content\Text\BBCode; use Friendica\Content\Text\BBCode;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Module\BaseApi; use Friendica\Module\BaseApi;
/** /**
@ -40,7 +39,7 @@ class Index extends BaseApi
$request = $this->getRequest([ $request = $this->getRequest([
'since_id' => 0, 'since_id' => 0,
'count' => 0, 'count' => 50,
], $request); ], $request);
$condition = ["`id` > ? AND `uid` = ?", $request['since_id'], $uid]; $condition = ["`id` > ? AND `uid` = ?", $request['since_id'], $uid];

View File

@ -32,7 +32,7 @@ use Friendica\Network\HTTPException;
*/ */
class Show extends BaseApi class Show extends BaseApi
{ {
protected function post(array $request = []) protected function rawContent(array $request = [])
{ {
BaseApi::checkAllowedScope(BaseApi::SCOPE_READ); BaseApi::checkAllowedScope(BaseApi::SCOPE_READ);
$uid = BaseApi::getCurrentUserID(); $uid = BaseApi::getCurrentUserID();

View File

@ -44,7 +44,7 @@ class Photo extends BaseApi
$this->friendicaPhoto = $friendicaPhoto; $this->friendicaPhoto = $friendicaPhoto;
} }
protected function post(array $request = []) protected function rawContent(array $request = [])
{ {
BaseApi::checkAllowedScope(BaseApi::SCOPE_READ); BaseApi::checkAllowedScope(BaseApi::SCOPE_READ);
$uid = BaseApi::getCurrentUserID(); $uid = BaseApi::getCurrentUserID();

View File

@ -56,7 +56,7 @@ class Conversation extends BaseApi
Logger::info(BaseApi::LOG_PREFIX . '{subaction}', ['module' => 'api', 'action' => 'conversation', 'subaction' => 'show', 'id' => $id]); Logger::info(BaseApi::LOG_PREFIX . '{subaction}', ['module' => 'api', 'action' => 'conversation', 'subaction' => 'show', 'id' => $id]);
// try to fetch the item for the local user - or the public item, if there is no local one // try to fetch the item for the local user - or the public item, if there is no local one
$item = Post::selectFirst(['parent-uri-id'], ['id' => $id]); $item = Post::selectFirst(['parent-uri-id'], ['uri-id' => $id]);
if (!DBA::isResult($item)) { if (!DBA::isResult($item)) {
throw new BadRequestException("There is no status with the id $id."); throw new BadRequestException("There is no status with the id $id.");
} }
@ -68,15 +68,15 @@ class Conversation extends BaseApi
$id = $parent['id']; $id = $parent['id'];
$condition = ["`parent` = ? AND `uid` IN (0, ?) AND `gravity` IN (?, ?) AND `id` > ?", $condition = ["`parent` = ? AND `uid` IN (0, ?) AND `gravity` IN (?, ?) AND `uri-id` > ?",
$id, $uid, GRAVITY_PARENT, GRAVITY_COMMENT, $since_id]; $id, $uid, GRAVITY_PARENT, GRAVITY_COMMENT, $since_id];
if ($max_id > 0) { if ($max_id > 0) {
$condition[0] .= " AND `id` <= ?"; $condition[0] .= " AND `uri-id` <= ?";
$condition[] = $max_id; $condition[] = $max_id;
} }
$params = ['order' => ['id' => true], 'limit' => [$start, $count]]; $params = ['order' => ['uri-id' => true], 'limit' => [$start, $count]];
$statuses = Post::selectForUser($uid, [], $condition, $params); $statuses = Post::selectForUser($uid, [], $condition, $params);
if (!DBA::isResult($statuses)) { if (!DBA::isResult($statuses)) {

View File

@ -59,8 +59,7 @@ class Block extends BaseApi
Contact\User::setBlocked($cdata['user'], $uid, true); Contact\User::setBlocked($cdata['user'], $uid, true);
// Mastodon-expected behavior: relationship is severed on block // Mastodon-expected behavior: relationship is severed on block
Contact::terminateFriendship($owner, $contact); Contact::terminateFriendship($contact);
Contact::revokeFollow($contact);
System::jsonExit(DI::mstdnRelationship()->createFromContactId($this->parameters['id'], $uid)->toArray()); System::jsonExit(DI::mstdnRelationship()->createFromContactId($this->parameters['id'], $uid)->toArray());
} }

View File

@ -40,7 +40,14 @@ class Unfollow extends BaseApi
DI::mstdnError()->UnprocessableEntity(); DI::mstdnError()->UnprocessableEntity();
} }
Contact::unfollow($this->parameters['id'], $uid); $cdata = Contact::getPublicAndUserContactID($this->parameters['id'], $uid);
if (empty($cdata['user'])) {
DI::mstdnError()->RecordNotFound();
}
$contact = Contact::getById($cdata['user']);
Contact::unfollow($contact);
System::jsonExit(DI::mstdnRelationship()->createFromContactId($this->parameters['id'], $uid)->toArray()); System::jsonExit(DI::mstdnRelationship()->createFromContactId($this->parameters['id'], $uid)->toArray());
} }

View File

@ -21,20 +21,44 @@
namespace Friendica\Module\Api\Mastodon; namespace Friendica\Module\Api\Mastodon;
use Friendica\App;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\L10n;
use Friendica\Core\System; use Friendica\Core\System;
use Friendica\Database\Database;
use Friendica\Module\Api\ApiResponse;
use Friendica\Module\BaseApi; use Friendica\Module\BaseApi;
use Friendica\Object\Api\Mastodon\Instance as InstanceEntity; use Friendica\Object\Api\Mastodon\Instance as InstanceEntity;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
/** /**
* @see https://docs.joinmastodon.org/api/rest/instances/ * @see https://docs.joinmastodon.org/api/rest/instances/
*/ */
class Instance extends BaseApi class Instance extends BaseApi
{ {
/** @var Database */
private $database;
/** @var IManageConfigValues */
private $config;
public function __construct(App $app, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, ApiResponse $response, Database $database, IManageConfigValues $config, array $server, array $parameters = [])
{
parent::__construct($app, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->database = $database;
$this->config = $config;
}
/** /**
* @param array $request
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \Friendica\Network\HTTPException\NotFoundException
* @throws \ImagickException
*/ */
protected function rawContent(array $request = []) protected function rawContent(array $request = [])
{ {
System::jsonExit(InstanceEntity::get()); System::jsonExit(new InstanceEntity($this->config, $this->baseUrl, $this->database));
} }
} }

View File

@ -36,12 +36,32 @@ class Accounts extends BaseApi
{ {
protected function delete(array $request = []) protected function delete(array $request = [])
{ {
$this->response->unsupported(Router::DELETE, $request); self::checkAllowedScope(self::SCOPE_WRITE);
$request = $this->getRequest([
'account_ids' => [], // Array of account IDs to remove from the list
], $request);
if (empty($request['account_ids']) || empty($this->parameters['id'])) {
DI::mstdnError()->UnprocessableEntity();
}
return Group::removeMembers($this->parameters['id'], $request['account_ids']);
} }
protected function post(array $request = []) protected function post(array $request = [])
{ {
$this->response->unsupported(Router::POST, $request); self::checkAllowedScope(self::SCOPE_WRITE);
$request = $this->getRequest([
'account_ids' => [], // Array of account IDs to add to the list
], $request);
if (empty($request['account_ids']) || empty($this->parameters['id'])) {
DI::mstdnError()->UnprocessableEntity();
}
return Group::addMembers($this->parameters['id'], $request['account_ids']);
} }
/** /**

View File

@ -21,8 +21,8 @@
namespace Friendica\Module\Api\Mastodon; namespace Friendica\Module\Api\Mastodon;
use Friendica\Content\Text\BBCode;
use Friendica\Content\Text\Markdown; use Friendica\Content\Text\Markdown;
use Friendica\Core\Protocol;
use Friendica\Core\System; use Friendica\Core\System;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\DI; use Friendica\DI;
@ -63,17 +63,12 @@ class Statuses extends BaseApi
// The imput is defined as text. So we can use Markdown for some enhancements // The imput is defined as text. So we can use Markdown for some enhancements
$body = Markdown::toBBCode($request['status']); $body = Markdown::toBBCode($request['status']);
// Avoids potential double expansion of existing links $item = [];
$body = BBCode::performWithEscapedTags($body, ['url'], function ($body) { $item['network'] = Protocol::DFRN;
return BBCode::expandTags($body);
});
$item = [];
$item['uid'] = $uid; $item['uid'] = $uid;
$item['verb'] = Activity::POST; $item['verb'] = Activity::POST;
$item['contact-id'] = $owner['id']; $item['contact-id'] = $owner['id'];
$item['author-id'] = $item['owner-id'] = Contact::getPublicIdByUserId($uid); $item['author-id'] = $item['owner-id'] = Contact::getPublicIdByUserId($uid);
$item['title'] = $request['spoiler_text'];
$item['body'] = $body; $item['body'] = $body;
if (!empty(self::getCurrentApplication()['name'])) { if (!empty(self::getCurrentApplication()['name'])) {
@ -114,14 +109,20 @@ class Statuses extends BaseApi
$item['private'] = Item::PRIVATE; $item['private'] = Item::PRIVATE;
break; break;
case 'direct': case 'direct':
// Direct messages are currently unsupported // The permissions are assigned in "expandTags"
DI::mstdnError()->InternalError('Direct messages are currently unsupported');
break; break;
default: default:
$item['allow_cid'] = $owner['allow_cid']; if (is_numeric($request['visibility']) && Group::exists($request['visibility'], $uid)) {
$item['allow_gid'] = $owner['allow_gid']; $item['allow_cid'] = '';
$item['deny_cid'] = $owner['deny_cid']; $item['allow_gid'] = '<' . $request['visibility'] . '>';
$item['deny_gid'] = $owner['deny_gid']; $item['deny_cid'] = '';
$item['deny_gid'] = '';
} else {
$item['allow_cid'] = $owner['allow_cid'];
$item['allow_gid'] = $owner['allow_gid'];
$item['deny_cid'] = $owner['deny_cid'];
$item['deny_gid'] = $owner['deny_gid'];
}
if (!empty($item['allow_cid'] . $item['allow_gid'] . $item['deny_cid'] . $item['deny_gid'])) { if (!empty($item['allow_cid'] . $item['allow_gid'] . $item['deny_cid'] . $item['deny_gid'])) {
$item['private'] = Item::PRIVATE; $item['private'] = Item::PRIVATE;
@ -139,16 +140,21 @@ class Statuses extends BaseApi
if ($request['in_reply_to_id']) { if ($request['in_reply_to_id']) {
$parent = Post::selectFirst(['uri'], ['uri-id' => $request['in_reply_to_id'], 'uid' => [0, $uid]]); $parent = Post::selectFirst(['uri'], ['uri-id' => $request['in_reply_to_id'], 'uid' => [0, $uid]]);
$item['thr-parent'] = $parent['uri']; $item['thr-parent'] = $parent['uri'];
$item['gravity'] = GRAVITY_COMMENT; $item['gravity'] = GRAVITY_COMMENT;
$item['object-type'] = Activity\ObjectType::COMMENT; $item['object-type'] = Activity\ObjectType::COMMENT;
$item['body'] = '[abstract=' . Protocol::ACTIVITYPUB . ']' . $request['spoiler_text'] . "[/abstract]\n" . $item['body'];
} else { } else {
self::checkThrottleLimit(); self::checkThrottleLimit();
$item['gravity'] = GRAVITY_PARENT; $item['gravity'] = GRAVITY_PARENT;
$item['object-type'] = Activity\ObjectType::NOTE; $item['object-type'] = Activity\ObjectType::NOTE;
$item['title'] = $request['spoiler_text'];
} }
$item = DI::contentItem()->expandTags($item, $request['visibility'] == 'direct');
if (!empty($request['media_ids'])) { if (!empty($request['media_ids'])) {
$item['object-type'] = Activity\ObjectType::IMAGE; $item['object-type'] = Activity\ObjectType::IMAGE;
$item['post-type'] = Item::PT_IMAGE; $item['post-type'] = Item::PT_IMAGE;

View File

@ -42,7 +42,7 @@ class Bookmark extends BaseApi
DI::mstdnError()->UnprocessableEntity(); DI::mstdnError()->UnprocessableEntity();
} }
$item = Post::selectFirstForUser($uid, ['id', 'gravity'], ['uri-id' => $this->parameters['id'], 'uid' => [$uid, 0]]); $item = Post::selectFirst(['uid', 'id', 'gravity'], ['uri-id' => $this->parameters['id'], 'uid' => [$uid, 0]], ['order' => ['uid' => true]]);
if (!DBA::isResult($item)) { if (!DBA::isResult($item)) {
DI::mstdnError()->RecordNotFound(); DI::mstdnError()->RecordNotFound();
} }
@ -51,6 +51,18 @@ class Bookmark extends BaseApi
DI::mstdnError()->UnprocessableEntity(DI::l10n()->t('Only starting posts can be bookmarked')); DI::mstdnError()->UnprocessableEntity(DI::l10n()->t('Only starting posts can be bookmarked'));
} }
if ($item['uid'] == 0) {
$stored = Item::storeForUserByUriId($this->parameters['id'], $uid);
if (!empty($stored)) {
$item = Post::selectFirst(['id', 'gravity'], ['id' => $stored]);
if (!DBA::isResult($item)) {
DI::mstdnError()->RecordNotFound();
}
} else {
DI::mstdnError()->RecordNotFound();
}
}
Item::update(['starred' => true], ['id' => $item['id']]); Item::update(['starred' => true], ['id' => $item['id']]);
System::jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid)->toArray()); System::jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid)->toArray());

View File

@ -42,7 +42,7 @@ class Unbookmark extends BaseApi
DI::mstdnError()->UnprocessableEntity(); DI::mstdnError()->UnprocessableEntity();
} }
$item = Post::selectFirstForUser($uid, ['id', 'gravity'], ['uri-id' => $this->parameters['id'], 'uid' => [$uid, 0]]); $item = Post::selectFirst(['uid', 'id', 'gravity'], ['uri-id' => $this->parameters['id'], 'uid' => [$uid, 0]], ['order' => ['uid' => true]]);
if (!DBA::isResult($item)) { if (!DBA::isResult($item)) {
DI::mstdnError()->RecordNotFound(); DI::mstdnError()->RecordNotFound();
} }
@ -51,6 +51,18 @@ class Unbookmark extends BaseApi
DI::mstdnError()->UnprocessableEntity(DI::l10n()->t('Only starting posts can be unbookmarked')); DI::mstdnError()->UnprocessableEntity(DI::l10n()->t('Only starting posts can be unbookmarked'));
} }
if ($item['uid'] == 0) {
$stored = Item::storeForUserByUriId($this->parameters['id'], $uid);
if (!empty($stored)) {
$item = Post::selectFirst(['id', 'gravity'], ['id' => $stored]);
if (!DBA::isResult($item)) {
DI::mstdnError()->RecordNotFound();
}
} else {
DI::mstdnError()->RecordNotFound();
}
}
Item::update(['starred' => false], ['id' => $item['id']]); Item::update(['starred' => false], ['id' => $item['id']]);
System::jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid)->toArray()); System::jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid)->toArray());

View File

@ -53,13 +53,13 @@ class Favorites extends BaseApi
$start = max(0, ($page - 1) * $count); $start = max(0, ($page - 1) * $count);
$condition = ["`uid` = ? AND `gravity` IN (?, ?) AND `id` > ? AND `starred`", $condition = ["`uid` = ? AND `gravity` IN (?, ?) AND `uri-id` > ? AND `starred`",
$uid, GRAVITY_PARENT, GRAVITY_COMMENT, $since_id]; $uid, GRAVITY_PARENT, GRAVITY_COMMENT, $since_id];
$params = ['order' => ['id' => true], 'limit' => [$start, $count]]; $params = ['order' => ['uri-id' => true], 'limit' => [$start, $count]];
if ($max_id > 0) { if ($max_id > 0) {
$condition[0] .= " AND `id` <= ?"; $condition[0] .= " AND `uri-id` <= ?";
$condition[] = $max_id; $condition[] = $max_id;
} }

View File

@ -23,6 +23,7 @@ namespace Friendica\Module\Api\Twitter\Favorites;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\Item; use Friendica\Model\Item;
use Friendica\Model\Post;
use Friendica\Module\BaseApi; use Friendica\Module\BaseApi;
use Friendica\Network\HTTPException\BadRequestException; use Friendica\Network\HTTPException\BadRequestException;
@ -42,9 +43,14 @@ class Create extends BaseApi
throw new BadRequestException('Item id not specified'); throw new BadRequestException('Item id not specified');
} }
Item::performActivity($id, 'like', $uid); $post = Post::selectFirst(['id'], ['uri-id' => $request['id'], 'uid' => [0, $uid]], ['order' => ['uid' => true]]);
if (empty($post['id'])) {
throw new BadRequestException('Item id not found');
}
$status_info = DI::twitterStatus()->createFromItemId($id, $uid)->toArray(); Item::performActivity($post['id'], 'like', $uid);
$status_info = DI::twitterStatus()->createFromUriId($id, $uid)->toArray();
$this->response->exit('status', ['status' => $status_info], $this->parameters['extension'] ?? null); $this->response->exit('status', ['status' => $status_info], $this->parameters['extension'] ?? null);
} }

View File

@ -23,6 +23,7 @@ namespace Friendica\Module\Api\Twitter\Favorites;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\Item; use Friendica\Model\Item;
use Friendica\Model\Post;
use Friendica\Module\BaseApi; use Friendica\Module\BaseApi;
use Friendica\Network\HTTPException\BadRequestException; use Friendica\Network\HTTPException\BadRequestException;
@ -42,9 +43,14 @@ class Destroy extends BaseApi
throw new BadRequestException('Item id not specified'); throw new BadRequestException('Item id not specified');
} }
Item::performActivity($id, 'unlike', $uid); $post = Post::selectFirst(['id'], ['uri-id' => $request['id'], 'uid' => [0, $uid]], ['order' => ['uid' => true]]);
if (empty($post['id'])) {
throw new BadRequestException('Item id not found');
}
$status_info = DI::twitterStatus()->createFromItemId($id, $uid)->toArray(); Item::performActivity($post['id'], 'unlike', $uid);
$status_info = DI::twitterStatus()->createFromUriId($id, $uid)->toArray();
$this->response->exit('status', ['status' => $status_info], $this->parameters['extension'] ?? null); $this->response->exit('status', ['status' => $status_info], $this->parameters['extension'] ?? null);
} }

View File

@ -22,13 +22,18 @@
namespace Friendica\Module\Api\Twitter\Friendships; namespace Friendica\Module\Api\Twitter\Friendships;
use Exception; use Exception;
use Friendica\App;
use Friendica\Core\L10n;
use Friendica\Core\Logger; use Friendica\Core\Logger;
use Friendica\DI; use Friendica\Factory\Api\Twitter\User as TwitterUser;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Friendica\Model\User; use Friendica\Model\User;
use Friendica\Module\Api\ApiResponse;
use Friendica\Module\Api\Twitter\ContactEndpoint; use Friendica\Module\Api\Twitter\ContactEndpoint;
use Friendica\Module\BaseApi; use Friendica\Module\BaseApi;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
/** /**
* Unfollow Contact * Unfollow Contact
@ -37,6 +42,16 @@ use Friendica\Network\HTTPException;
*/ */
class Destroy extends ContactEndpoint class Destroy extends ContactEndpoint
{ {
/** @var TwitterUser */
private $twitterUser;
public function __construct(App $app, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, ApiResponse $response, TwitterUser $twitterUser, array $server, array $parameters = [])
{
parent::__construct($app, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->twitterUser = $twitterUser;
}
protected function post(array $request = []) protected function post(array $request = [])
{ {
BaseApi::checkAllowedScope(BaseApi::SCOPE_WRITE); BaseApi::checkAllowedScope(BaseApi::SCOPE_WRITE);
@ -66,18 +81,9 @@ class Destroy extends ContactEndpoint
$user = $this->twitterUser->createFromContactId($contact_id, $uid, true)->toArray(); $user = $this->twitterUser->createFromContactId($contact_id, $uid, true)->toArray();
try { try {
$result = Contact::terminateFriendship($owner, $contact); Contact::unfollow($contact);
if ($result === null) {
Logger::notice(BaseApi::LOG_PREFIX . 'Not supported for {network}', ['module' => 'api', 'action' => 'friendships_destroy', 'network' => $contact['network']]);
throw new HTTPException\ExpectationFailedException('Unfollowing is currently not supported by this contact\'s network.');
}
if ($result === false) {
throw new HTTPException\ServiceUnavailableException('Unable to unfollow this contact, please retry in a few minutes or contact your administrator.');
}
} catch (Exception $e) { } catch (Exception $e) {
Logger::error(BaseApi::LOG_PREFIX . $e->getMessage(), ['owner' => $owner, 'contact' => $contact]); Logger::error(BaseApi::LOG_PREFIX . $e->getMessage(), ['contact' => $contact]);
throw new HTTPException\InternalServerErrorException('Unable to unfollow this contact, please contact your administrator'); throw new HTTPException\InternalServerErrorException('Unable to unfollow this contact, please contact your administrator');
} }

View File

@ -46,32 +46,32 @@ class Incoming extends ContactEndpoint
$max_id = $this->getRequestValue($request, 'max_id', 0, 0); $max_id = $this->getRequestValue($request, 'max_id', 0, 0);
$min_id = $this->getRequestValue($request, 'min_id', 0, 0); $min_id = $this->getRequestValue($request, 'min_id', 0, 0);
$params = ['order' => ['cid' => true], 'limit' => $count]; $params = ['order' => ['contact-id' => true], 'limit' => $count];
$condition = ['uid' => $uid, 'pending' => true]; $condition = ["`uid` = ? AND NOT `blocked` AND NOT `ignore` AND `contact-id` != 0 AND (`suggest-cid` = 0 OR `suggest-cid` IS NULL)", $uid];
$total_count = (int)DBA::count('user-contact', $condition); $total_count = (int)DBA::count('intro', $condition);
if (!empty($max_id)) { if (!empty($max_id)) {
$condition = DBA::mergeConditions($condition, ["`cid` < ?", $max_id]); $condition = DBA::mergeConditions($condition, ["`contact-id` < ?", $max_id]);
} }
if (!empty($since_id)) { if (!empty($since_id)) {
$condition = DBA::mergeConditions($condition, ["`cid` > ?", $since_id]); $condition = DBA::mergeConditions($condition, ["`contact-id` > ?", $since_id]);
} }
if (!empty($min_id)) { if (!empty($min_id)) {
$condition = DBA::mergeConditions($condition, ["`cid` > ?", $min_id]); $condition = DBA::mergeConditions($condition, ["`contact-id` > ?", $min_id]);
$params['order'] = ['cid']; $params['order'] = ['contact-id'];
} }
$ids = []; $ids = [];
$contacts = DBA::select('user-contact', ['cid'], $condition, $params); $contacts = DBA::select('intro', ['contact-id'], $condition, $params);
while ($contact = DBA::fetch($contacts)) { while ($contact = DBA::fetch($contacts)) {
self::setBoundaries($contact['cid']); self::setBoundaries($contact['contact-id']);
$ids[] = $contact['cid']; $ids[] = $contact['contact-id'];
} }
DBA::close($contacts); DBA::close($contacts);

View File

@ -56,7 +56,7 @@ class Ownership extends BaseApi
BaseApi::checkAllowedScope(BaseApi::SCOPE_READ); BaseApi::checkAllowedScope(BaseApi::SCOPE_READ);
$uid = BaseApi::getCurrentUserID(); $uid = BaseApi::getCurrentUserID();
$groups = $this->dba->select('group', [], ['deleted' => false, 'uid' => $uid]); $groups = $this->dba->select('group', [], ['deleted' => false, 'uid' => $uid, 'cid' => null]);
// loop through all groups // loop through all groups
$lists = []; $lists = [];

View File

@ -78,10 +78,10 @@ class Statuses extends BaseApi
$groups = $this->dba->selectToArray('group_member', ['contact-id'], ['gid' => $request['list_id']]); $groups = $this->dba->selectToArray('group_member', ['contact-id'], ['gid' => $request['list_id']]);
$gids = array_column($groups, 'contact-id'); $gids = array_column($groups, 'contact-id');
$condition = ['uid' => $uid, 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT], 'contact-id' => $gids]; $condition = ['uid' => $uid, 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT], 'contact-id' => $gids];
$condition = DBA::mergeConditions($condition, ["`id` > ?", $since_id]); $condition = DBA::mergeConditions($condition, ["`uri-id` > ?", $since_id]);
if ($max_id > 0) { if ($max_id > 0) {
$condition[0] .= " AND `id` <= ?"; $condition[0] .= " AND `uri-id` <= ?";
$condition[] = $max_id; $condition[] = $max_id;
} }
if ($exclude_replies) { if ($exclude_replies) {
@ -89,11 +89,11 @@ class Statuses extends BaseApi
$condition[] = GRAVITY_PARENT; $condition[] = GRAVITY_PARENT;
} }
if ($conversation_id > 0) { if ($conversation_id > 0) {
$condition[0] .= " AND `parent` = ?"; $condition[0] .= " AND `parent-uri-id` = ?";
$condition[] = $conversation_id; $condition[] = $conversation_id;
} }
$params = ['order' => ['id' => true], 'limit' => [$start, $count]]; $params = ['order' => ['uri-id' => true], 'limit' => [$start, $count]];
$statuses = Post::selectForUser($uid, [], $condition, $params); $statuses = Post::selectForUser($uid, [], $condition, $params);
$items = []; $items = [];

View File

@ -59,10 +59,10 @@ class Tweets extends BaseApi
$start = max(0, ($page - 1) * $count); $start = max(0, ($page - 1) * $count);
$params = ['order' => ['id' => true], 'limit' => [$start, $count]]; $params = ['order' => ['uri-id' => true], 'limit' => [$start, $count]];
if (preg_match('/^#(\w+)$/', $searchTerm, $matches) === 1 && isset($matches[1])) { if (preg_match('/^#(\w+)$/', $searchTerm, $matches) === 1 && isset($matches[1])) {
$searchTerm = $matches[1]; $searchTerm = $matches[1];
$condition = ["`iid` > ? AND `name` = ? AND (NOT `private` OR (`private` AND `uid` = ?))", $since_id, $searchTerm, $uid]; $condition = ["`uri-id` > ? AND `name` = ? AND (NOT `private` OR (`private` AND `uid` = ?))", $since_id, $searchTerm, $uid];
$tags = DBA::select('tag-search-view', ['uri-id'], $condition); $tags = DBA::select('tag-search-view', ['uri-id'], $condition);
$uriids = []; $uriids = [];
@ -83,13 +83,13 @@ class Tweets extends BaseApi
$params['group_by'] = ['uri-id']; $params['group_by'] = ['uri-id'];
} else { } else {
$condition = ["`id` > ? $condition = ["`uri-id` > ?
" . ($exclude_replies ? " AND `gravity` = " . GRAVITY_PARENT : ' ') . " " . ($exclude_replies ? " AND `gravity` = " . GRAVITY_PARENT : ' ') . "
AND (`uid` = 0 OR (`uid` = ? AND NOT `global`)) AND (`uid` = 0 OR (`uid` = ? AND NOT `global`))
AND `body` LIKE CONCAT('%',?,'%')", AND `body` LIKE CONCAT('%',?,'%')",
$since_id, $uid, $_REQUEST['q']]; $since_id, $uid, $_REQUEST['q']];
if ($max_id > 0) { if ($max_id > 0) {
$condition[0] .= ' AND `id` <= ?'; $condition[0] .= ' AND `uri-id` <= ?';
$condition[] = $max_id; $condition[] = $max_id;
} }
} }

View File

@ -25,6 +25,7 @@ use Friendica\Module\BaseApi;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Friendica\Model\Item; use Friendica\Model\Item;
use Friendica\Model\Post;
use Friendica\Network\HTTPException\BadRequestException; use Friendica\Network\HTTPException\BadRequestException;
/** /**
@ -45,13 +46,18 @@ class Destroy extends BaseApi
throw new BadRequestException('An id is missing.'); throw new BadRequestException('An id is missing.');
} }
$post = Post::selectFirst(['id'], ['uri-id' => $request['id'], 'uid' => [0, $uid]], ['order' => ['uid' => true]]);
if (empty($post['id'])) {
throw new BadRequestException('Item id not found');
}
$this->logger->notice('API: api_statuses_destroy: ' . $id); $this->logger->notice('API: api_statuses_destroy: ' . $id);
$include_entities = $this->getRequestValue($request, 'include_entities', false); $include_entities = $this->getRequestValue($request, 'include_entities', false);
$ret = DI::twitterStatus()->createFromItemId($id, $uid, $include_entities)->toArray(); $ret = DI::twitterStatus()->createFromUriId($id, $uid, $include_entities)->toArray();
Item::deleteForUser(['id' => $id], $uid); Item::deleteForUser(['id' => $post['id']], $uid);
$this->response->exit('status', ['status' => $ret], $this->parameters['extension'] ?? null, Contact::getPublicIdByUserId($uid)); $this->response->exit('status', ['status' => $ret], $this->parameters['extension'] ?? null, Contact::getPublicIdByUserId($uid));
} }

View File

@ -53,11 +53,11 @@ class HomeTimeline extends BaseApi
$start = max(0, ($page - 1) * $count); $start = max(0, ($page - 1) * $count);
$condition = ["`uid` = ? AND `gravity` IN (?, ?) AND `id` > ?", $condition = ["`uid` = ? AND `gravity` IN (?, ?) AND `uri-id` > ?",
$uid, GRAVITY_PARENT, GRAVITY_COMMENT, $since_id]; $uid, GRAVITY_PARENT, GRAVITY_COMMENT, $since_id];
if ($max_id > 0) { if ($max_id > 0) {
$condition[0] .= " AND `id` <= ?"; $condition[0] .= " AND `uri-id` <= ?";
$condition[] = $max_id; $condition[] = $max_id;
} }
if ($exclude_replies) { if ($exclude_replies) {
@ -65,11 +65,11 @@ class HomeTimeline extends BaseApi
$condition[] = GRAVITY_PARENT; $condition[] = GRAVITY_PARENT;
} }
if ($conversation_id > 0) { if ($conversation_id > 0) {
$condition[0] .= " AND `parent` = ?"; $condition[0] .= " AND `parent-uri-id` = ?";
$condition[] = $conversation_id; $condition[] = $conversation_id;
} }
$params = ['order' => ['id' => true], 'limit' => [$start, $count]]; $params = ['order' => ['uri-id' => true], 'limit' => [$start, $count]];
$statuses = Post::selectForUser($uid, [], $condition, $params); $statuses = Post::selectForUser($uid, [], $condition, $params);
$ret = []; $ret = [];

View File

@ -52,7 +52,7 @@ class Mentions extends BaseApi
$query = "`gravity` IN (?, ?) AND `uri-id` IN $query = "`gravity` IN (?, ?) AND `uri-id` IN
(SELECT `uri-id` FROM `post-user-notification` WHERE `uid` = ? AND `notification-type` & ? != 0 ORDER BY `uri-id`) (SELECT `uri-id` FROM `post-user-notification` WHERE `uid` = ? AND `notification-type` & ? != 0 ORDER BY `uri-id`)
AND (`uid` = 0 OR (`uid` = ? AND NOT `global`)) AND `id` > ?"; AND (`uid` = 0 OR (`uid` = ? AND NOT `global`)) AND `uri-id` > ?";
$condition = [ $condition = [
GRAVITY_PARENT, GRAVITY_COMMENT, GRAVITY_PARENT, GRAVITY_COMMENT,
@ -64,13 +64,13 @@ class Mentions extends BaseApi
]; ];
if ($max_id > 0) { if ($max_id > 0) {
$query .= " AND `id` <= ?"; $query .= " AND `uri-id` <= ?";
$condition[] = $max_id; $condition[] = $max_id;
} }
array_unshift($condition, $query); array_unshift($condition, $query);
$params = ['order' => ['id' => true], 'limit' => [$start, $count]]; $params = ['order' => ['uri-id' => true], 'limit' => [$start, $count]];
$statuses = Post::selectForUser($uid, [], $condition, $params); $statuses = Post::selectForUser($uid, [], $condition, $params);
$ret = []; $ret = [];

View File

@ -46,15 +46,15 @@ class NetworkPublicTimeline extends BaseApi
$start = max(0, ($page - 1) * $count); $start = max(0, ($page - 1) * $count);
$condition = ["`uid` = 0 AND `gravity` IN (?, ?) AND `id` > ? AND `private` = ?", $condition = ["`uid` = 0 AND `gravity` IN (?, ?) AND `uri-id` > ? AND `private` = ?",
GRAVITY_PARENT, GRAVITY_COMMENT, $since_id, Item::PUBLIC]; GRAVITY_PARENT, GRAVITY_COMMENT, $since_id, Item::PUBLIC];
if ($max_id > 0) { if ($max_id > 0) {
$condition[0] .= " AND `id` <= ?"; $condition[0] .= " AND `uri-id` <= ?";
$condition[] = $max_id; $condition[] = $max_id;
} }
$params = ['order' => ['id' => true], 'limit' => [$start, $count]]; $params = ['order' => ['uri-id' => true], 'limit' => [$start, $count]];
$statuses = Post::selectForUser($uid, Item::DISPLAY_FIELDLIST, $condition, $params); $statuses = Post::selectForUser($uid, Item::DISPLAY_FIELDLIST, $condition, $params);
$ret = []; $ret = [];

View File

@ -52,30 +52,30 @@ class PublicTimeline extends BaseApi
$start = max(0, ($page - 1) * $count); $start = max(0, ($page - 1) * $count);
if ($exclude_replies && !$conversation_id) { if ($exclude_replies && !$conversation_id) {
$condition = ["`gravity` = ? AND `id` > ? AND `private` = ? AND `wall` AND NOT `author-hidden`", $condition = ["`gravity` = ? AND `uri-id` > ? AND `private` = ? AND `wall` AND NOT `author-hidden`",
GRAVITY_PARENT, $since_id, Item::PUBLIC]; GRAVITY_PARENT, $since_id, Item::PUBLIC];
if ($max_id > 0) { if ($max_id > 0) {
$condition[0] .= " AND `id` <= ?"; $condition[0] .= " AND `uri-id` <= ?";
$condition[] = $max_id; $condition[] = $max_id;
} }
$params = ['order' => ['id' => true], 'limit' => [$start, $count]]; $params = ['order' => ['uri-id' => true], 'limit' => [$start, $count]];
$statuses = Post::selectForUser($uid, [], $condition, $params); $statuses = Post::selectForUser($uid, [], $condition, $params);
} else { } else {
$condition = ["`gravity` IN (?, ?) AND `id` > ? AND `private` = ? AND `wall` AND `origin` AND NOT `author-hidden`", $condition = ["`gravity` IN (?, ?) AND `uri-id` > ? AND `private` = ? AND `wall` AND `origin` AND NOT `author-hidden`",
GRAVITY_PARENT, GRAVITY_COMMENT, $since_id, Item::PUBLIC]; GRAVITY_PARENT, GRAVITY_COMMENT, $since_id, Item::PUBLIC];
if ($max_id > 0) { if ($max_id > 0) {
$condition[0] .= " AND `id` <= ?"; $condition[0] .= " AND `uri-id` <= ?";
$condition[] = $max_id; $condition[] = $max_id;
} }
if ($conversation_id > 0) { if ($conversation_id > 0) {
$condition[0] .= " AND `parent` = ?"; $condition[0] .= " AND `parent-uri-id` = ?";
$condition[] = $conversation_id; $condition[] = $conversation_id;
} }
$params = ['order' => ['id' => true], 'limit' => [$start, $count]]; $params = ['order' => ['uri-id' => true], 'limit' => [$start, $count]];
$statuses = Post::selectForUser($uid, [], $condition, $params); $statuses = Post::selectForUser($uid, [], $condition, $params);
} }

View File

@ -50,8 +50,8 @@ class Retweet extends BaseApi
throw new BadRequestException('An id is missing.'); throw new BadRequestException('An id is missing.');
} }
$fields = ['uri-id', 'network', 'body', 'title', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink']; $fields = ['id', 'uri-id', 'network', 'body', 'title', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink'];
$item = Post::selectFirst($fields, ['id' => $id, 'private' => [Item::PUBLIC, Item::UNLISTED]]); $item = Post::selectFirst($fields, ['uri-id' => $id, 'uid' => [0, $uid], 'private' => [Item::PUBLIC, Item::UNLISTED]], ['order' => ['uid' => true]]);
if (DBA::isResult($item) && !empty($item['body'])) { if (DBA::isResult($item) && !empty($item['body'])) {
if (in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::TWITTER])) { if (in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::TWITTER])) {
@ -59,7 +59,7 @@ class Retweet extends BaseApi
throw new InternalServerErrorException(); throw new InternalServerErrorException();
} }
$item_id = $id; $item_id = $item['id'];
} else { } else {
$item_id = Diaspora::performReshare($item['uri-id'], $uid); $item_id = Diaspora::performReshare($item['uri-id'], $uid);
} }

View File

@ -52,23 +52,18 @@ class Show extends BaseApi
$conversation = !empty($request['conversation']); $conversation = !empty($request['conversation']);
// try to fetch the item for the local user - or the public item, if there is no local one // try to fetch the item for the local user - or the public item, if there is no local one
$uri_item = Post::selectFirst(['uri-id'], ['id' => $id]); $item = Post::selectFirst(['id'], ['uri-id' => $id, 'uid' => [0, $uid]], ['order' => ['uid' => true]]);
if (!DBA::isResult($uri_item)) {
throw new BadRequestException(sprintf("There is no status with the id %d", $id));
}
$item = Post::selectFirst(['id'], ['uri-id' => $uri_item['uri-id'], 'uid' => [0, $uid]], ['order' => ['uid' => true]]);
if (!DBA::isResult($item)) { if (!DBA::isResult($item)) {
throw new BadRequestException(sprintf("There is no status with the uri-id %d for the given user.", $uri_item['uri-id'])); throw new BadRequestException(sprintf("There is no status with the uri-id %d for the given user.", $id));
} }
$id = $item['id']; $item_id = $item['id'];
if ($conversation) { if ($conversation) {
$condition = ['parent' => $id, 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT]]; $condition = ['parent' => $item_id, 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT]];
$params = ['order' => ['id' => true]]; $params = ['order' => ['uri-id' => true]];
} else { } else {
$condition = ['id' => $id, 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT]]; $condition = ['id' => $item_id, 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT]];
$params = []; $params = [];
} }

View File

@ -21,9 +21,9 @@
namespace Friendica\Module\Api\Twitter\Statuses; namespace Friendica\Module\Api\Twitter\Statuses;
use Friendica\Content\Text\BBCode;
use Friendica\Content\Text\HTML; use Friendica\Content\Text\HTML;
use Friendica\Content\Text\Markdown; use Friendica\Content\Text\Markdown;
use Friendica\Core\Protocol;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\Contact; use Friendica\Model\Contact;
@ -78,17 +78,12 @@ class Update extends BaseApi
$body = Markdown::toBBCode($request['status']); $body = Markdown::toBBCode($request['status']);
} }
// Avoids potential double expansion of existing links
$body = BBCode::performWithEscapedTags($body, ['url'], function ($body) {
return BBCode::expandTags($body);
});
$item = []; $item = [];
$item['network'] = Protocol::DFRN;
$item['uid'] = $uid; $item['uid'] = $uid;
$item['verb'] = Activity::POST; $item['verb'] = Activity::POST;
$item['contact-id'] = $owner['id']; $item['contact-id'] = $owner['id'];
$item['author-id'] = Contact::getPublicIdByUserId($uid); $item['author-id'] = $item['owner-id'] = Contact::getPublicIdByUserId($uid);
$item['owner-id'] = $item['author-id'];
$item['title'] = $request['title']; $item['title'] = $request['title'];
$item['body'] = $body; $item['body'] = $body;
$item['app'] = $request['source']; $item['app'] = $request['source'];
@ -115,7 +110,7 @@ class Update extends BaseApi
} }
if ($request['in_reply_to_status_id']) { if ($request['in_reply_to_status_id']) {
$parent = Post::selectFirst(['uri'], ['id' => $request['in_reply_to_status_id'], 'uid' => [0, $uid]]); $parent = Post::selectFirst(['uri'], ['uri-id' => $request['in_reply_to_status_id'], 'uid' => [0, $uid]]);
$item['thr-parent'] = $parent['uri']; $item['thr-parent'] = $parent['uri'];
$item['gravity'] = GRAVITY_COMMENT; $item['gravity'] = GRAVITY_COMMENT;
@ -127,6 +122,8 @@ class Update extends BaseApi
$item['object-type'] = Activity\ObjectType::NOTE; $item['object-type'] = Activity\ObjectType::NOTE;
} }
$item = DI::contentItem()->expandTags($item);
if (!empty($request['media_ids'])) { if (!empty($request['media_ids'])) {
$ids = explode(',', $request['media_ids']); $ids = explode(',', $request['media_ids']);
} elseif (!empty($_FILES['media'])) { } elseif (!empty($_FILES['media'])) {

View File

@ -53,7 +53,7 @@ class UserTimeline extends BaseApi
$start = max(0, ($page - 1) * $count); $start = max(0, ($page - 1) * $count);
$condition = ["(`uid` = ? OR (`uid` = ? AND NOT `global`)) AND `gravity` IN (?, ?) AND `id` > ? AND `author-id` = ?", $condition = ["(`uid` = ? OR (`uid` = ? AND NOT `global`)) AND `gravity` IN (?, ?) AND `uri-id` > ? AND `author-id` = ?",
0, $uid, GRAVITY_PARENT, GRAVITY_COMMENT, $since_id, $cid]; 0, $uid, GRAVITY_PARENT, GRAVITY_COMMENT, $since_id, $cid];
if ($exclude_replies) { if ($exclude_replies) {
@ -62,15 +62,15 @@ class UserTimeline extends BaseApi
} }
if ($conversation_id > 0) { if ($conversation_id > 0) {
$condition[0] .= " AND `parent` = ?"; $condition[0] .= " AND `parent-uri-id` = ?";
$condition[] = $conversation_id; $condition[] = $conversation_id;
} }
if ($max_id > 0) { if ($max_id > 0) {
$condition[0] .= " AND `id` <= ?"; $condition[0] .= " AND `uri-id` <= ?";
$condition[] = $max_id; $condition[] = $max_id;
} }
$params = ['order' => ['id' => true], 'limit' => [$start, $count]]; $params = ['order' => ['uri-id' => true], 'limit' => [$start, $count]];
$statuses = Post::selectForUser($uid, [], $condition, $params); $statuses = Post::selectForUser($uid, [], $condition, $params);
$ret = []; $ret = [];

View File

@ -29,7 +29,7 @@ use Friendica\Content\Pager;
use Friendica\Core\L10n; use Friendica\Core\L10n;
use Friendica\Core\Renderer; use Friendica\Core\Renderer;
use Friendica\Core\System; use Friendica\Core\System;
use Friendica\Navigation\Notifications\ValueObject\FormattedNotification; use Friendica\Navigation\Notifications\ValueObject\FormattedNotify;
use Friendica\Network\HTTPException\ForbiddenException; use Friendica\Network\HTTPException\ForbiddenException;
use Friendica\Util\Profiler; use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
@ -43,29 +43,29 @@ abstract class BaseNotifications extends BaseModule
{ {
/** @var array Array of URL parameters */ /** @var array Array of URL parameters */
const URL_TYPES = [ const URL_TYPES = [
FormattedNotification::NETWORK => 'network', FormattedNotify::NETWORK => 'network',
FormattedNotification::SYSTEM => 'system', FormattedNotify::SYSTEM => 'system',
FormattedNotification::HOME => 'home', FormattedNotify::HOME => 'home',
FormattedNotification::PERSONAL => 'personal', FormattedNotify::PERSONAL => 'personal',
FormattedNotification::INTRO => 'intros', FormattedNotify::INTRO => 'intros',
]; ];
/** @var array Array of the allowed notifications and their printable name */ /** @var array Array of the allowed notifications and their printable name */
const PRINT_TYPES = [ const PRINT_TYPES = [
FormattedNotification::NETWORK => 'Network', FormattedNotify::NETWORK => 'Network',
FormattedNotification::SYSTEM => 'System', FormattedNotify::SYSTEM => 'System',
FormattedNotification::HOME => 'Home', FormattedNotify::HOME => 'Home',
FormattedNotification::PERSONAL => 'Personal', FormattedNotify::PERSONAL => 'Personal',
FormattedNotification::INTRO => 'Introductions', FormattedNotify::INTRO => 'Introductions',
]; ];
/** @var array The array of access keys for notification pages */ /** @var array The array of access keys for notification pages */
const ACCESS_KEYS = [ const ACCESS_KEYS = [
FormattedNotification::NETWORK => 'w', FormattedNotify::NETWORK => 'w',
FormattedNotification::SYSTEM => 'y', FormattedNotify::SYSTEM => 'y',
FormattedNotification::HOME => 'h', FormattedNotify::HOME => 'h',
FormattedNotification::PERSONAL => 'r', FormattedNotify::PERSONAL => 'r',
FormattedNotification::INTRO => 'i', FormattedNotify::INTRO => 'i',
]; ];
/** @var int The default count of items per page */ /** @var int The default count of items per page */

View File

@ -558,7 +558,7 @@ class Contact extends BaseModule
'details' => $contact['location'], 'details' => $contact['location'],
'tags' => $contact['keywords'], 'tags' => $contact['keywords'],
'about' => $contact['about'], 'about' => $contact['about'],
'account_type' => Model\Contact::getAccountType($contact), 'account_type' => Model\Contact::getAccountType($contact['contact-type']),
'sparkle' => $sparkle, 'sparkle' => $sparkle,
'itemurl' => ($contact['addr'] ?? '') ?: $contact['url'], 'itemurl' => ($contact['addr'] ?? '') ?: $contact['url'],
'network' => ContactSelector::networkToName($contact['network'], $contact['url'], $contact['protocol'], $contact['gsid']), 'network' => ContactSelector::networkToName($contact['network'], $contact['url'], $contact['protocol'], $contact['gsid']),

View File

@ -101,7 +101,7 @@ class Hovercard extends BaseModule
'network_link' => Strings::formatNetworkName($contact['network'], $contact['url']), 'network_link' => Strings::formatNetworkName($contact['network'], $contact['url']),
'tags' => $contact['keywords'], 'tags' => $contact['keywords'],
'bd' => $contact['bd'] <= DBA::NULL_DATE ? '' : $contact['bd'], 'bd' => $contact['bd'] <= DBA::NULL_DATE ? '' : $contact['bd'],
'account_type' => Contact::getAccountType($contact), 'account_type' => Contact::getAccountType($contact['contact-type']),
'actions' => $actions, 'actions' => $actions,
], ],
]); ]);

View File

@ -364,7 +364,7 @@ class Profile extends BaseModule
'$url' => $url, '$url' => $url,
'$profileurllabel' => $this->t('Profile URL'), '$profileurllabel' => $this->t('Profile URL'),
'$profileurl' => $contact['url'], '$profileurl' => $contact['url'],
'$account_type' => Contact::getAccountType($contact), '$account_type' => Contact::getAccountType($contact['contact-type']),
'$location' => BBCode::convertForUriId($contact['uri-id'] ?? 0, $contact['location']), '$location' => BBCode::convertForUriId($contact['uri-id'] ?? 0, $contact['location']),
'$location_label' => $this->t('Location:'), '$location_label' => $this->t('Location:'),
'$xmpp' => BBCode::convertForUriId($contact['uri-id'] ?? 0, $contact['xmpp']), '$xmpp' => BBCode::convertForUriId($contact['uri-id'] ?? 0, $contact['xmpp']),

View File

@ -38,7 +38,10 @@ use Psr\Log\LoggerInterface;
class Revoke extends BaseModule class Revoke extends BaseModule
{ {
/** @var array */ /**
* User-specific contact (uid != 0) array
* @var array
*/
protected $contact; protected $contact;
/** @var Database */ /** @var Database */
@ -82,14 +85,9 @@ class Revoke extends BaseModule
self::checkFormSecurityTokenRedirectOnError('contact/' . $this->parameters['id'], 'contact_revoke'); self::checkFormSecurityTokenRedirectOnError('contact/' . $this->parameters['id'], 'contact_revoke');
$result = Model\Contact::revokeFollow($this->contact); Model\Contact::revokeFollow($this->contact);
if ($result === true) {
notice($this->t('Follow was successfully revoked.')); notice($this->t('Follow was successfully revoked.'));
} elseif ($result === null) {
notice($this->t('Follow was successfully revoked, however the remote contact won\'t be aware of this revokation.'));
} else {
notice($this->t('Unable to revoke follow, please try again later or contact the administrator.'));
}
$this->baseUrl->redirect('contact/' . $this->parameters['id']); $this->baseUrl->redirect('contact/' . $this->parameters['id']);
} }

View File

@ -119,7 +119,7 @@ class Network extends BaseModule
if (self::$forumContactId) { if (self::$forumContactId) {
// If self::$forumContactId belongs to a communitity forum or a privat goup,.add a mention to the status editor // If self::$forumContactId belongs to a communitity forum or a privat goup,.add a mention to the status editor
$condition = ["`id` = ? AND (`forum` OR `prv`)", self::$forumContactId]; $condition = ["`id` = ? AND `contact-type` = ?", self::$forumContactId, Contact::TYPE_COMMUNITY];
$contact = DBA::selectFirst('contact', ['addr'], $condition); $contact = DBA::selectFirst('contact', ['addr'], $condition);
if (!empty($contact['addr'])) { if (!empty($contact['addr'])) {
$content = '!' . $contact['addr']; $content = '!' . $contact['addr'];

View File

@ -114,6 +114,10 @@ class ActivityPubConversion extends BaseModule
$object_data['thread-completion'] = $activity['thread-completion']; $object_data['thread-completion'] = $activity['thread-completion'];
} }
if (!empty($activity['completion-mode'])) {
$object_data['completion-mode'] = $activity['completion-mode'];
}
$results[] = [ $results[] = [
'title' => DI::l10n()->t('Object data'), 'title' => DI::l10n()->t('Object data'),
'content' => visible_whitespace(var_export($object_data, true)) 'content' => visible_whitespace(var_export($object_data, true))

View File

@ -78,7 +78,7 @@ class Receive extends BaseModule
$this->logger->info('Diaspora: Dispatching.'); $this->logger->info('Diaspora: Dispatching.');
Diaspora::dispatchPublic($msg); Diaspora::dispatchPublic($msg, Diaspora::PUSHED);
} }
/** /**
@ -92,8 +92,19 @@ class Receive extends BaseModule
$this->logger->info('Diaspora: Receiving post.'); $this->logger->info('Diaspora: Receiving post.');
$importer = User::getByGuid($this->parameters['guid']); $importer = User::getByGuid($this->parameters['guid']);
if (empty($importer)) {
// We haven't found the user.
// To avoid the remote system trying again we send the message that we accepted the content.
throw new HTTPException\AcceptedException();
}
$msg = $this->decodePost(false, $importer['prvkey'] ?? ''); if ($importer['account-type'] == User::ACCOUNT_TYPE_COMMUNITY) {
// Communities aren't working with the Diaspora protoccol
// We throw an "accepted" here, so that the sender doesn't repeat the delivery
throw new HTTPException\AcceptedException();
}
$msg = $this->decodePost(false, $importer['prvkey']);
$this->logger->info('Diaspora: Dispatching.'); $this->logger->info('Diaspora: Dispatching.');

View File

@ -165,7 +165,7 @@ class Directory extends BaseModule
'img_hover' => $contact['name'], 'img_hover' => $contact['name'],
'name' => $contact['name'], 'name' => $contact['name'],
'details' => $details, 'details' => $details,
'account_type' => Model\Contact::getAccountType($contact), 'account_type' => Model\Contact::getAccountType($contact['contact-type']),
'profile' => $profile, 'profile' => $profile,
'location' => $location_e, 'location' => $location_e,
'tags' => $contact['pub_keywords'], 'tags' => $contact['pub_keywords'],

View File

@ -21,18 +21,45 @@
namespace Friendica\Module\Notifications; namespace Friendica\Module\Notifications;
use Friendica\App;
use Friendica\BaseModule; use Friendica\BaseModule;
use Friendica\Contact\Introduction\Repository\Introduction;
use Friendica\Core\L10n;
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
use Friendica\Core\System; use Friendica\Core\System;
use Friendica\DI;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Friendica\Module\Response;
use Friendica\Module\Security\Login; use Friendica\Module\Security\Login;
use Friendica\Navigation\Notifications\Factory;
use Friendica\Navigation\Notifications\Repository;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
/**
* Interacting with the /notification command
*/
class Notification extends BaseModule class Notification extends BaseModule
{ {
/** @var Introduction */
private $introductionRepo;
/** @var Repository\Notification */
private $notificationRepo;
/** @var Repository\Notify */
private $notifyRepo;
/** @var IManagePersonalConfigValues */
private $pconfig;
/** @var Factory\Notification */
private $notificationFactory;
public function __construct(Introduction $introductionRepo, Repository\Notification $notificationRepo, Factory\Notification $notificationFactory, Repository\Notify $notifyRepo, IManagePersonalConfigValues $pconfig, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
{
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->introductionRepo = $introductionRepo;
$this->notificationRepo = $notificationRepo;
$this->notificationFactory = $notificationFactory;
$this->notifyRepo = $notifyRepo;
$this->pconfig = $pconfig;
}
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
@ -45,26 +72,26 @@ class Notification extends BaseModule
protected function post(array $request = []) protected function post(array $request = [])
{ {
if (!local_user()) { if (!local_user()) {
throw new HTTPException\UnauthorizedException(DI::l10n()->t('Permission denied.')); throw new HTTPException\UnauthorizedException($this->l10n->t('Permission denied.'));
} }
$request_id = $this->parameters['id'] ?? false; $request_id = $this->parameters['id'] ?? false;
if ($request_id) { if ($request_id) {
$intro = DI::intro()->selectOneById($request_id, local_user()); $intro = $this->introductionRepo->selectOneById($request_id, local_user());
switch ($_POST['submit']) { switch ($_POST['submit']) {
case DI::l10n()->t('Discard'): case $this->l10n->t('Discard'):
Contact\Introduction::discard($intro); Contact\Introduction::discard($intro);
DI::intro()->delete($intro); $this->introductionRepo->delete($intro);
break; break;
case DI::l10n()->t('Ignore'): case $this->l10n->t('Ignore'):
$intro->ignore(); $intro->ignore();
DI::intro()->save($intro); $this->introductionRepo->save($intro);
break; break;
} }
DI::baseUrl()->redirect('notifications/intros'); $this->baseUrl->redirect('notifications/intros');
} }
} }
@ -76,15 +103,15 @@ class Notification extends BaseModule
protected function rawContent(array $request = []) protected function rawContent(array $request = [])
{ {
if (!local_user()) { if (!local_user()) {
throw new HTTPException\UnauthorizedException(DI::l10n()->t('Permission denied.')); throw new HTTPException\UnauthorizedException($this->l10n->t('Permission denied.'));
} }
if (DI::args()->get(1) === 'mark' && DI::args()->get(2) === 'all') { if ($this->args->get(1) === 'mark' && $this->args->get(2) === 'all') {
try { try {
DI::notification()->setAllSeenForUser(local_user()); $this->notificationRepo->setAllSeenForUser(local_user());
$success = DI::notify()->setAllSeenForUser(local_user()); $success = $this->notifyRepo->setAllSeenForUser(local_user());
} catch (\Exception $e) { } catch (\Exception $e) {
DI::logger()->warning('set all seen failed.', ['exception' => $e]); $this->logger->warning('set all seen failed.', ['exception' => $e]);
$success = false; $success = false;
} }
@ -104,38 +131,71 @@ class Notification extends BaseModule
protected function content(array $request = []): string protected function content(array $request = []): string
{ {
if (!local_user()) { if (!local_user()) {
notice(DI::l10n()->t('You must be logged in to show this page.')); notice($this->l10n->t('You must be logged in to show this page.'));
return Login::form(); return Login::form();
} }
$request_id = $this->parameters['id'] ?? false; if (isset($this->parameters['notify_id'])) {
$this->handleNotify($this->parameters['notify_id']);
if ($request_id) { } elseif (isset($this->parameters['id'])) {
$Notify = DI::notify()->selectOneById($request_id); $this->handleNotification($this->parameters['id']);
if ($Notify->uid !== local_user()) {
throw new HTTPException\ForbiddenException();
}
if (DI::pConfig()->get(local_user(), 'system', 'detailed_notif')) {
$Notify->setSeen();
DI::notify()->save($Notify);
} else {
if ($Notify->uriId) {
DI::notification()->setAllSeenForUser($Notify->uid, ['target-uri-id' => $Notify->uriId]);
}
DI::notify()->setAllSeenForRelatedNotify($Notify);
}
if ((string)$Notify->link) {
System::externalRedirect($Notify->link);
}
DI::baseUrl()->redirect();
} }
DI::baseUrl()->redirect('notifications/system'); $this->baseUrl->redirect('notifications/system');
return ''; return '';
} }
private function handleNotify(int $notifyId)
{
$Notify = $this->notifyRepo->selectOneById($notifyId);
if ($Notify->uid !== local_user()) {
throw new HTTPException\ForbiddenException();
}
if ($this->pconfig->get(local_user(), 'system', 'detailed_notif')) {
$Notify->setSeen();
$this->notifyRepo->save($Notify);
} else {
if ($Notify->uriId) {
$this->notificationRepo->setAllSeenForUser($Notify->uid, ['target-uri-id' => $Notify->uriId]);
}
$this->notifyRepo->setAllSeenForRelatedNotify($Notify);
}
if ((string)$Notify->link) {
System::externalRedirect($Notify->link);
}
$this->baseUrl->redirect();
}
private function handleNotification(int $notificationId)
{
$Notification = $this->notificationRepo->selectOneById($notificationId);
if ($Notification->uid !== local_user()) {
throw new HTTPException\ForbiddenException();
}
if ($this->pconfig->get(local_user(), 'system', 'detailed_notif')) {
$Notification->setSeen();
$this->notificationRepo->save($Notification);
} else {
if ($Notification->parentUriId) {
$this->notificationRepo->setAllSeenForUser($Notification->uid, ['parent-uri-id' => $Notification->parentUriId]);
} else {
$Notification->setSeen();
$this->notificationRepo->save($Notification);
}
}
$message = $this->notificationFactory->getMessageFromNotification($Notification);
if ($message['link']) {
System::externalRedirect($message['link']);
}
$this->baseUrl->redirect();
}
} }

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