Merge branch 'stable' into develop

This commit is contained in:
Tobias Diekershoff 2024-08-17 17:29:18 +02:00
commit 95229140f8
194 changed files with 11224 additions and 9691 deletions

View file

@ -22,9 +22,11 @@
namespace Friendica\Module\ActivityPub;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Item;
use Friendica\Model\User;
use Friendica\Module\BaseApi;
use Friendica\Module\Special\HTTPException;
@ -103,6 +105,7 @@ class Inbox extends BaseApi
$uid = 0;
}
Item::incrementInbound(Protocol::ACTIVITYPUB);
ActivityPub\Receiver::processInbox($postdata, $_SERVER, $uid);
throw new \Friendica\Network\HTTPException\AcceptedException();

View file

@ -28,6 +28,12 @@ use Friendica\Module\BaseAdmin;
class Index extends BaseAdmin
{
protected function post(array $request = [])
{
// @todo check if POST is really used here
$this->content($request);
}
protected function content(array $request = []): string
{
parent::content();

View file

@ -30,6 +30,12 @@ use Friendica\Util\Strings;
class Details extends BaseAdmin
{
protected function post(array $request = [])
{
// @todo check if POST is really used here
$this->content($request);
}
protected function content(array $request = []): string
{
parent::content();

View file

@ -29,6 +29,12 @@ use Friendica\Util\Strings;
class Index extends BaseAdmin
{
protected function post(array $request = [])
{
// @todo check if POST is really used here
$this->content($request);
}
protected function content(array $request = []): string
{
parent::content();

View file

@ -43,9 +43,9 @@ class Block extends BaseApi
Contact\User::setBlocked($this->parameters['id'], $uid, true);
$cdata = Contact::getPublicAndUserContactID($this->parameters['id'], $uid);
if (!empty($cdata['user'])) {
$contact = Contact::getById($cdata['user']);
$ucid = Contact::getUserContactId($this->parameters['id'], $uid);
if ($ucid) {
$contact = Contact::getById($ucid);
if (!empty($contact)) {
// Mastodon-expected behavior: relationship is severed on block
Contact::terminateFriendship($contact);

View file

@ -21,7 +21,7 @@
namespace Friendica\Module\Api\Mastodon\Accounts;
use Friendica\Core\System;
use Friendica\Content\Widget;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
@ -75,6 +75,9 @@ class Followers extends BaseApi
$params['order'] = ['pid'];
}
$networks = Widget::unavailableNetworks();
$condition = DBA::mergeConditions($condition, array_merge(["NOT `network` IN (" . substr(str_repeat("?, ", count($networks)), 0, -2) . ")"], $networks));
$accounts = [];
foreach (Contact::selectAccountToArray(['pid'], $condition, $params) as $follower) {

View file

@ -21,7 +21,7 @@
namespace Friendica\Module\Api\Mastodon\Accounts;
use Friendica\Core\System;
use Friendica\Content\Widget;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
@ -75,6 +75,9 @@ class Following extends BaseApi
$params['order'] = ['pid'];
}
$networks = Widget::unavailableNetworks();
$condition = DBA::mergeConditions($condition, array_merge(["NOT `network` IN (" . substr(str_repeat("?, ", count($networks)), 0, -2) . ")"], $networks));
$accounts = [];
foreach (Contact::selectAccountToArray(['pid'], $condition, $params) as $follower) {

View file

@ -51,9 +51,9 @@ class Lists extends BaseApi
$lists = [];
$cdata = Contact::getPublicAndUserContactID($id, $uid);
if (!empty($cdata['user'])) {
$circles = DBA::select('group_member', ['gid'], ['contact-id' => $cdata['user']]);
$ucid = Contact::getUserContactId($id, $uid);
if ($ucid) {
$circles = DBA::select('group_member', ['gid'], ['contact-id' => $ucid]);
while ($circle = DBA::fetch($circles)) {
$lists[] = DI::mstdnList()->createFromCircleId($circle['gid']);
}

View file

@ -45,12 +45,12 @@ class Note extends BaseApi
'comment' => '',
], $request);
$cdata = Contact::getPublicAndUserContactID($this->parameters['id'], $uid);
if (empty($cdata['user'])) {
$ucid = Contact::getUserContactId($this->parameters['id'], $uid);
if (!$ucid) {
$this->logAndJsonError(404, $this->errorFactory->RecordNotFound());
}
Contact::update(['info' => $request['comment']], ['id' => $cdata['user']]);
Contact::update(['info' => $request['comment']], ['id' => $ucid]);
$this->jsonExit(DI::mstdnRelationship()->createFromContactId($this->parameters['id'], $uid)->toArray());
}

View file

@ -40,12 +40,12 @@ class Unfollow extends BaseApi
$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity());
}
$cdata = Contact::getPublicAndUserContactID($this->parameters['id'], $uid);
if (empty($cdata['user'])) {
$ucid = Contact::getUserContactId($this->parameters['id'], $uid);
if (!$ucid) {
$this->logAndJsonError(404, $this->errorFactory->RecordNotFound());
}
$contact = Contact::getById($cdata['user']);
$contact = Contact::getById($ucid);
Contact::unfollow($contact);

View file

@ -100,12 +100,12 @@ class UpdateCredentials extends BaseApi
User::update($user, $uid);
Profile::update($profile, $uid);
$cdata = Contact::getPublicAndUserContactID($owner['id'], $uid);
if (empty($cdata)) {
$ucid = Contact::getUserContactId($owner['id'], $uid);
if (!$ucid) {
DI::mstdnError()->InternalError();
}
$account = DI::mstdnAccount()->createFromContactId($cdata['user'], $uid);
$account = DI::mstdnAccount()->createFromContactId($ucid, $uid);
$this->response->addJsonContent($account->toArray());
}
}

View file

@ -45,13 +45,13 @@ class VerifyCredentials extends BaseApi
DI::mstdnError()->InternalError();
}
$cdata = Contact::getPublicAndUserContactID($self['id'], $uid);
if (empty($cdata)) {
$ucid = Contact::getUserContactId($self['id'], $uid);
if (!$ucid) {
DI::mstdnError()->InternalError();
}
// @todo Support the source property,
$account = DI::mstdnAccount()->createFromContactId($cdata['user'], $uid);
$account = DI::mstdnAccount()->createFromContactId($ucid, $uid);
$this->response->addJsonContent($account->toArray());
}
}

View file

@ -21,7 +21,6 @@
namespace Friendica\Module\Api\Mastodon;
use Friendica\Core\System;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Module\BaseApi;
@ -47,12 +46,12 @@ class FollowRequests extends BaseApi
$this->checkAllowedScope(self::SCOPE_FOLLOW);
$uid = self::getCurrentUserID();
$cdata = Contact::getPublicAndUserContactID($this->parameters['id'], $uid);
if (empty($cdata['user'])) {
$ucid = Contact::getUserContactId($this->parameters['id'], $uid);
if (!$ucid) {
throw new HTTPException\NotFoundException('Contact not found');
}
$introduction = DI::intro()->selectForContact($cdata['user']);
$introduction = DI::intro()->selectForContact($ucid);
$contactId = $introduction->cid;

View file

@ -131,12 +131,19 @@ class InstanceV2 extends BaseApi
return new InstanceEntity\Configuration(
$statuses_config,
new InstanceEntity\MediaAttachmentsConfig(Images::supportedMimeTypes(), $image_size_limit, $image_matrix_limit),
new InstanceEntity\MediaAttachmentsConfig($this->supportedMimeTypes(), $image_size_limit, $image_matrix_limit),
new InstanceEntity\Polls(),
new InstanceEntity\Accounts(),
);
}
private function supportedMimeTypes(): array
{
$mimetypes = ['audio/aac', 'audio/flac', 'audio/mpeg', 'audio/mp4', 'audio/ogg', 'audio/wav',
'audio/webm', 'video/mp4', 'video/ogg', 'video/webm'];
return array_merge(Images::supportedMimeTypes(), $mimetypes);
}
private function buildContactInfo(): InstanceEntity\Contact
{
$email = implode(',', User::getAdminEmailList());

View file

@ -22,8 +22,9 @@
namespace Friendica\Module\Api\Mastodon;
use Friendica\Core\Logger;
use Friendica\Core\System;
use Friendica\DI;
use Friendica\Model\Attach;
use Friendica\Model\Contact;
use Friendica\Model\Photo;
use Friendica\Model\Post;
use Friendica\Module\BaseApi;
@ -51,14 +52,38 @@ class Media extends BaseApi
$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity());
}
$media = Photo::upload($uid, $_FILES['file'], '', null, null, '', '', $request['description']);
if (empty($media)) {
$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity());
$type = Post\Media::getType($_FILES['file']['type']);
if (in_array($type, [Post\Media::IMAGE, Post\Media::UNKNOWN])) {
$media = Photo::upload($uid, $_FILES['file'], '', null, null, '', '', $request['description']);
if (empty($media)) {
$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity());
}
Logger::info('Uploaded photo', ['media' => $media]);
$this->jsonExit(DI::mstdnAttachment()->createFromPhoto($media['id']));
} else {
$tempFileName = $_FILES['file']['tmp_name'];
$fileName = basename($_FILES['file']['name']);
$fileSize = intval($_FILES['file']['size']);
$maxFileSize = DI::config()->get('system', 'maxfilesize');
if ($fileSize <= 0) {
@unlink($tempFileName);
$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity());
}
if ($maxFileSize && $fileSize > $maxFileSize) {
@unlink($tempFileName);
$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity());
}
$id = Attach::storeFile($tempFileName, self::getCurrentUserID(), $fileName, $_FILES['file']['type'], '<' . Contact::getPublicIdByUserId(self::getCurrentUserID()) . '>');
@unlink($tempFileName);
Logger::info('Uploaded media', ['id' => $id]);
$this->jsonExit(DI::mstdnAttachment()->createFromAttach($id));
}
Logger::info('Uploaded photo', ['media' => $media]);
$this->jsonExit(DI::mstdnAttachment()->createFromPhoto($media['id']));
}
public function put(array $request = [])
@ -77,6 +102,10 @@ class Media extends BaseApi
$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity());
}
if (DI::mstdnAttachment()->isAttach($this->parameters['id']) && Attach::exists(['id' => substr($this->parameters['id'], 7)])) {
$this->jsonExit(DI::mstdnAttachment()->createFromAttach(substr($this->parameters['id'], 7)));
}
$photo = Photo::selectFirst(['resource-id'], ['id' => $this->parameters['id'], 'uid' => $uid]);
if (empty($photo['resource-id'])) {
$media = Post\Media::getById($this->parameters['id']);
@ -108,10 +137,15 @@ class Media extends BaseApi
}
$id = $this->parameters['id'];
if (!Photo::exists(['id' => $id, 'uid' => $uid])) {
$this->logAndJsonError(404, $this->errorFactory->RecordNotFound());
if (Photo::exists(['id' => $id, 'uid' => $uid])) {
$this->jsonExit(DI::mstdnAttachment()->createFromPhoto($id));
}
$this->jsonExit(DI::mstdnAttachment()->createFromPhoto($id));
if (DI::mstdnAttachment()->isAttach($id) && Attach::exists(['id' => substr($id, 7)])) {
$this->jsonExit(DI::mstdnAttachment()->createFromAttach(substr($id, 7)));
}
$this->logAndJsonError(404, $this->errorFactory->RecordNotFound());
}
}

View file

@ -98,13 +98,13 @@ class PushSubscription extends BaseApi
}
$fields = [
Notification::TYPE_FOLLOW => $request['data']['alerts'][Notification::TYPE_FOLLOW] ?? false,
Notification::TYPE_LIKE => $request['data']['alerts'][Notification::TYPE_LIKE] ?? false,
Notification::TYPE_RESHARE => $request['data']['alerts'][Notification::TYPE_RESHARE] ?? false,
Notification::TYPE_MENTION => $request['data']['alerts'][Notification::TYPE_MENTION] ?? false,
Notification::TYPE_POLL => $request['data']['alerts'][Notification::TYPE_POLL] ?? false,
Notification::TYPE_INTRODUCTION => $request['data']['alerts'][Notification::TYPE_INTRODUCTION] ?? false,
Notification::TYPE_POST => $request['data']['alerts'][Notification::TYPE_POST] ?? false,
Notification::TYPE_FOLLOW => $this->setBoolean($request['data']['alerts'][Notification::TYPE_FOLLOW] ?? false),
Notification::TYPE_LIKE => $this->setBoolean($request['data']['alerts'][Notification::TYPE_LIKE] ?? false),
Notification::TYPE_RESHARE => $this->setBoolean($request['data']['alerts'][Notification::TYPE_RESHARE] ?? false),
Notification::TYPE_MENTION => $this->setBoolean($request['data']['alerts'][Notification::TYPE_MENTION] ?? false),
Notification::TYPE_POLL => $this->setBoolean($request['data']['alerts'][Notification::TYPE_POLL] ?? false),
Notification::TYPE_INTRODUCTION => $this->setBoolean($request['data']['alerts'][Notification::TYPE_INTRODUCTION] ?? false),
Notification::TYPE_POST => $this->setBoolean($request['data']['alerts'][Notification::TYPE_POST] ?? false),
];
$ret = Subscription::update($application['id'], $uid, $fields);
@ -120,6 +120,14 @@ class PushSubscription extends BaseApi
$this->response->addJsonContent($subscriptionObj->toArray());
}
private function setBoolean($input): bool
{
if (is_bool($input)) {
return $input;
}
return strtolower($input) == 'true';
}
protected function delete(array $request = []): void
{
$this->checkAllowedScope(self::SCOPE_PUSH);

View file

@ -28,6 +28,7 @@ use Friendica\Core\Protocol;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Attach;
use Friendica\Model\Contact;
use Friendica\Model\Circle;
use Friendica\Model\Item;
@ -397,6 +398,20 @@ class Statuses extends BaseApi
$item['attachments'] = [];
foreach ($media_ids as $id) {
if (DI::mstdnAttachment()->isAttach($id) && Attach::exists(['id' => substr($id, 7)])) {
$attach = Attach::selectFirst([], ['id' => substr($id, 7)]);
$attachment = [
'type' => Post\Media::getType($attach['filetype']),
'mimetype' => $attach['filetype'],
'url' => DI::baseUrl() . '/attach/' . substr($id, 7),
'size' => $attach['filetype'],
'name' => $attach['filename']
];
$item['attachments'][] = $attachment;
Attach::setPermissionForId(substr($id, 7), $item['uid'], $item['allow_cid'], $item['allow_gid'], $item['deny_cid'], $item['deny_gid']);
continue;
}
$media = DBA::toArray(DBA::p("SELECT `resource-id`, `scale`, `type`, `desc`, `filename`, `datasize`, `width`, `height` FROM `photo`
WHERE `resource-id` IN (SELECT `resource-id` FROM `photo` WHERE `id` = ?) AND `photo`.`uid` = ?
ORDER BY `photo`.`width` DESC LIMIT 2", $id, $item['uid']));
@ -409,13 +424,16 @@ class Statuses extends BaseApi
$ext = Images::getExtensionByMimeType($media[0]['type']);
$attachment = ['type' => Post\Media::IMAGE, 'mimetype' => $media[0]['type'],
'url' => DI::baseUrl() . '/photo/' . $media[0]['resource-id'] . '-' . $media[0]['scale'] . $ext,
'size' => $media[0]['datasize'],
'name' => $media[0]['filename'] ?: $media[0]['resource-id'],
$attachment = [
'type' => Post\Media::IMAGE,
'mimetype' => $media[0]['type'],
'url' => DI::baseUrl() . '/photo/' . $media[0]['resource-id'] . '-' . $media[0]['scale'] . $ext,
'size' => $media[0]['datasize'],
'name' => $media[0]['filename'] ?: $media[0]['resource-id'],
'description' => $media[0]['desc'] ?? '',
'width' => $media[0]['width'],
'height' => $media[0]['height']];
'width' => $media[0]['width'],
'height' => $media[0]['height']
];
if (count($media) > 1) {
$attachment['preview'] = DI::baseUrl() . '/photo/' . $media[1]['resource-id'] . '-' . $media[1]['scale'] . $ext;

View file

@ -110,8 +110,7 @@ class ListTimeline extends BaseApi
private function getStatusesForGroup(int $uid, array $request): array
{
$cdata = Contact::getPublicAndUserContactID((int)substr($this->parameters['id'], 6), $uid);
$cid = $cdata['public'];
$cid = Contact::getPublicContactId((int)substr($this->parameters['id'], 6), $uid);
$condition = ["(`uid` = ? OR (`uid` = ? AND NOT `global`))", 0, $uid];

View file

@ -21,26 +21,47 @@
namespace Friendica\Module\Api\Mastodon\Timelines;
use Friendica\App;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\L10n;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Item;
use Friendica\Model\Post;
use Friendica\Module\Api\ApiResponse;
use Friendica\Module\BaseApi;
use Friendica\Module\Conversation\Community;
use Friendica\Network\HTTPException;
use Friendica\Object\Api\Mastodon\TimelineOrderByTypes;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
/**
* @see https://docs.joinmastodon.org/methods/timelines/
*/
class PublicTimeline extends BaseApi
{
/**
* @var IManageConfigValues
*/
private $config;
public function __construct(IManageConfigValues $config, \Friendica\Factory\Api\Mastodon\Error $errorFactory, App $app, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, ApiResponse $response, array $server, array $parameters = [])
{
parent::__construct($errorFactory, $app, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->config = $config;
}
/**
* @throws HTTPException\InternalServerErrorException
*/
protected function rawContent(array $request = [])
{
if ($this->config->get('system', 'block_public') || $this->config->get('system', 'community_page_style') == Community::DISABLED_VISITOR) {
$this->checkAllowedScope(BaseApi::SCOPE_READ);
}
$uid = self::getCurrentUserID();
$request = $this->getRequest([
@ -56,6 +77,10 @@ class PublicTimeline extends BaseApi
'friendica_order' => TimelineOrderByTypes::ID, // Sort order options (defaults to ID)
], $request);
if (!$this->localAllowed() && !$this->globalAllowed()) {
$this->jsonExit([]);
}
$condition = [
'gravity' => [Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT], 'private' => Item::PUBLIC,
'network' => Protocol::FEDERATED, 'author-blocked' => false, 'author-hidden' => false
@ -64,13 +89,13 @@ class PublicTimeline extends BaseApi
$condition = $this->addPagingConditions($request, $condition);
$params = $this->buildOrderAndLimitParams($request);
if ($request['local']) {
if ($request['local'] && $this->localAllowed()) {
$condition = DBA::mergeConditions($condition, ['origin' => true]);
} else {
$condition = DBA::mergeConditions($condition, ['uid' => 0]);
}
if ($request['remote']) {
if ($request['remote'] && $this->globalAllowed()) {
$condition = DBA::mergeConditions($condition, ["NOT `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `origin` AND `post-user`.`uri-id` = `post-timeline-view`.`uri-id`)"]);
}
@ -113,4 +138,14 @@ class PublicTimeline extends BaseApi
self::setLinkHeader($request['friendica_order'] != TimelineOrderByTypes::ID);
$this->jsonExit($statuses);
}
private function localAllowed(): bool
{
return in_array($this->config->get('system', 'community_page_style'), [Community::LOCAL, Community::LOCAL_AND_GLOBAL, Community::DISABLED_VISITOR]);
}
private function globalAllowed(): bool
{
return in_array($this->config->get('system', 'community_page_style'), [Community::GLOBAL, Community::LOCAL_AND_GLOBAL, Community::DISABLED_VISITOR]);
}
}

View file

@ -21,25 +21,46 @@
namespace Friendica\Module\Api\Mastodon\Trends;
use Friendica\App;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\L10n;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Post;
use Friendica\Module\Api\ApiResponse;
use Friendica\Module\BaseApi;
use Friendica\Module\Conversation\Community;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
/**
* @see https://docs.joinmastodon.org/methods/trends/#statuses
*/
class Statuses extends BaseApi
{
/**
* @var IManageConfigValues
*/
private $config;
public function __construct(IManageConfigValues $config, \Friendica\Factory\Api\Mastodon\Error $errorFactory, App $app, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, ApiResponse $response, array $server, array $parameters = [])
{
parent::__construct($errorFactory, $app, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->config = $config;
}
/**
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
protected function rawContent(array $request = [])
{
if ($this->config->get('system', 'block_public') || $this->config->get('system', 'community_page_style') == Community::DISABLED_VISITOR) {
$this->checkAllowedScope(BaseApi::SCOPE_READ);
}
$uid = self::getCurrentUserID();
$request = $this->getRequest([

View file

@ -47,7 +47,8 @@ class Destroy extends BaseApi
$this->dba = $dba;
}
protected function rawContent(array $request = [])
protected function post(array $request = [])
{
$this->checkAllowedScope(BaseApi::SCOPE_WRITE);
$uid = BaseApi::getCurrentUserID();

View file

@ -54,7 +54,7 @@ class NewDM extends BaseApi
$this->directMessage = $directMessage;
}
protected function rawContent(array $request = [])
protected function post(array $request = [])
{
$this->checkAllowedScope(BaseApi::SCOPE_WRITE);
$uid = BaseApi::getCurrentUserID();
@ -81,9 +81,9 @@ class NewDM extends BaseApi
}
}
$cdata = Contact::getPublicAndUserContactID($cid, $uid);
$ucid = Contact::getUserContactId($cid, $uid);
$id = Mail::send($uid, $cdata['user'], $request['text'], $sub, $replyto);
$id = Mail::send($uid, $ucid, $request['text'], $sub, $replyto);
if ($id > -1) {
$ret = $this->directMessage->createFromMailId($id, $uid, $this->getRequestValue($request, 'getText', ''));

View file

@ -88,9 +88,9 @@ abstract class DirectMessagesEndpoint extends BaseApi
$cid = BaseApi::getContactIDForSearchterm($this->getRequestValue($request, 'screen_name', ''), $this->getRequestValue($request, 'profileurl', ''), $this->getRequestValue($request, 'user_id', 0), 0);
if (!empty($cid)) {
$cdata = Contact::getPublicAndUserContactID($cid, $uid);
if (!empty($cdata['user'])) {
$condition = DBA::mergeConditions($condition, ["`contact-id` = ?", $cdata['user']]);
$ucid = Contact::getUserContactId($cid, $uid);
if ($ucid) {
$condition = DBA::mergeConditions($condition, ["`contact-id` = ?", $ucid]);
}
}

View file

@ -71,13 +71,13 @@ class Destroy extends ContactEndpoint
}
// Get Contact by given id
$cdata = Contact::getPublicAndUserContactID($contact_id, $uid);
if (!empty($cdata['user'])) {
$ucid = Contact::getUserContactId($contact_id, $uid);
if (!$ucid) {
Logger::notice(BaseApi::LOG_PREFIX . 'Not following contact', ['module' => 'api', 'action' => 'friendships_destroy']);
throw new HTTPException\NotFoundException('Not following Contact');
}
$contact = Contact::getById($cdata['user']);
$contact = Contact::getById($ucid);
$user = $this->twitterUser->createFromContactId($contact_id, $uid, true)->toArray();
try {

View file

@ -55,9 +55,9 @@ class Show extends ContactEndpoint
$following = false;
if ($source_cid == Contact::getPublicIdByUserId($uid)) {
$cdata = Contact::getPublicAndUserContactID($target_cid, $uid);
if (!empty($cdata['user'])) {
$usercontact = Contact::getById($cdata['user'], ['rel']);
$ucid = Contact::getUserContactId($target_cid, $uid);
if ($ucid) {
$usercontact = Contact::getById($ucid, ['rel']);
switch ($usercontact['rel'] ?? Contact::NOTHING) {
case Contact::FOLLOWER:
$follower = true;

View file

@ -54,7 +54,7 @@ class Create extends BaseApi
$this->friendicaCircle = $friendicaCircle;
}
protected function rawContent(array $request = [])
protected function post(array $request = [])
{
$this->checkAllowedScope(BaseApi::SCOPE_WRITE);
$uid = BaseApi::getCurrentUserID();

View file

@ -54,7 +54,7 @@ class Destroy extends BaseApi
$this->friendicaCircle = $friendicaCircle;
}
protected function rawContent(array $request = [])
protected function post(array $request = [])
{
$this->checkAllowedScope(BaseApi::SCOPE_WRITE);
$uid = BaseApi::getCurrentUserID();

View file

@ -54,7 +54,7 @@ class Update extends BaseApi
$this->friendicaCircle = $friendicaCircle;
}
protected function rawContent(array $request = [])
protected function post(array $request = [])
{
$this->checkAllowedScope(BaseApi::SCOPE_WRITE);
$uid = BaseApi::getCurrentUserID();

View file

@ -58,7 +58,13 @@ class Contact extends BaseModule
return;
}
$redirectUrl = $_POST['redirect_url'] ?? 'contact';
$redirectUrl = $_POST['command'] ?? '';
if (substr($redirectUrl, 0, 7) != 'contact') {
$redirectUrl = 'contact';
}
if (!empty($_POST['parameter'])) {
$redirectUrl .= '?' . $_POST['parameter'];
}
self::checkFormSecurityTokenRedirectOnError($redirectUrl, 'contact_batch_actions');
@ -253,7 +259,7 @@ class Contact extends BaseModule
$sql_extra = " AND `archive` AND NOT `blocked` AND NOT `pending`";
break;
case 'pending':
$sql_extra = " AND `pending` AND NOT `archive` AND NOT `failed` AND ((`rel` = ?)
$sql_extra = " AND `pending` AND NOT `archive` AND ((`rel` = ?)
OR `id` IN (SELECT `contact-id` FROM `intro` WHERE `intro`.`uid` = ? AND NOT `ignore`))";
$sql_values[] = Model\Contact::SHARING;
$sql_values[] = DI::userSession()->getLocalUserId();
@ -459,6 +465,7 @@ class Contact extends BaseModule
'$finding' => $searching ? DI::l10n()->t('Results for: %s', $search) : '',
'$submit' => DI::l10n()->t('Find'),
'$cmd' => DI::args()->getCommand(),
'$parameter' => http_build_query($request),
'$contacts' => $contacts,
'$form_security_token' => BaseModule::getFormSecurityToken('contact_batch_actions'),
'multiselect' => 1,

View file

@ -81,18 +81,18 @@ class Conversations extends BaseModule
// Backward compatibility: Ensure to use the public contact when the user contact is provided
// Remove by version 2022.03
$data = Model\Contact::getPublicAndUserContactID(intval($this->parameters['id']), $this->userSession->getLocalUserId());
if (empty($data)) {
$pcid = Model\Contact::getPublicContactId(intval($this->parameters['id']), $this->userSession->getLocalUserId());
if (!$pcid) {
throw new NotFoundException($this->t('Contact not found.'));
}
$contact = Model\Contact::getById($data['public']);
$contact = Model\Contact::getById($pcid);
if (empty($contact)) {
throw new NotFoundException($this->t('Contact not found.'));
}
// Don't display contacts that are about to be deleted
if (!empty($contact['deleted']) || !empty($contact['network']) && $contact['network'] == Protocol::PHANTOM) {
if ($contact['deleted'] || $contact['network'] == Protocol::PHANTOM) {
throw new NotFoundException($this->t('Contact not found.'));
}

View file

@ -215,7 +215,7 @@ class Follow extends BaseModule
$this->baseUrl->redirect($returnPath);
} elseif (!empty($result['cid'])) {
$this->baseUrl->redirect('contact/' . $result['cid']);
$this->baseUrl->redirect('contact/' . Contact::getPublicContactId($result['cid'], $this->session->getLocalUserId()));
}
$this->sysMessages->addNotice($this->t('The contact could not be added.'));

View file

@ -65,7 +65,7 @@ class Media extends BaseModule
$o = Contact::getTabsHTML($contact, Contact::TAB_MEDIA);
$o .= ModelContact::getPostsFromUrl($contact['url'], $this->userSession->getLocalUserId(), true);
$o .= ModelContact::getPostsFromUrl($contact['url'], $this->userSession->getLocalUserId(), true, $request['last_created'] ?? '');
return $o;
}

View file

@ -73,18 +73,18 @@ class Posts extends BaseModule
// Backward compatibility: Ensure to use the public contact when the user contact is provided
// Remove by version 2022.03
$data = Model\Contact::getPublicAndUserContactID(intval($this->parameters['id']), $this->userSession->getLocalUserId());
if (empty($data)) {
$pcid = Model\Contact::getPublicContactId(intval($this->parameters['id']), $this->userSession->getLocalUserId());
if (!$pcid) {
throw new NotFoundException($this->t('Contact not found.'));
}
$contact = Model\Contact::getById($data['public']);
$contact = Model\Contact::getById($pcid);
if (!DBA::isResult($contact)) {
throw new NotFoundException($this->t('Contact not found.'));
}
// Don't display contacts that are about to be deleted
if (DBA::isResult($contact) && (!empty($contact['deleted']) || !empty($contact['network']) && $contact['network'] == Protocol::PHANTOM)) {
if ($contact['deleted'] || $contact['network'] == Protocol::PHANTOM) {
throw new NotFoundException($this->t('Contact not found.'));
}

View file

@ -91,8 +91,8 @@ class Profile extends BaseModule
// Backward compatibility: The update still needs a user-specific contact ID
// Change to user-contact table check by version 2022.03
$cdata = Contact::getPublicAndUserContactID($contact_id, $this->session->getLocalUserId());
if (empty($cdata['user']) || !$this->db->exists('contact', ['id' => $cdata['user'], 'deleted' => false])) {
$ucid = Contact::getUserContactId($contact_id, $this->session->getLocalUserId());
if (!$ucid || !$this->db->exists('contact', ['id' => $ucid, 'deleted' => false])) {
return;
}
@ -134,14 +134,14 @@ class Profile extends BaseModule
}
if (isset($request['channel_frequency'])) {
Contact\User::setChannelFrequency($cdata['user'], $this->session->getLocalUserId(), $request['channel_frequency']);
Contact\User::setChannelFrequency($ucid, $this->session->getLocalUserId(), $request['channel_frequency']);
}
if (isset($request['channel_only'])) {
Contact\User::setChannelOnly($cdata['user'], $this->session->getLocalUserId(), $request['channel_only']);
Contact\User::setChannelOnly($ucid, $this->session->getLocalUserId(), $request['channel_only']);
}
if (!Contact::update($fields, ['id' => $cdata['user'], 'uid' => $this->session->getLocalUserId()])) {
if (!Contact::update($fields, ['id' => $ucid, 'uid' => $this->session->getLocalUserId()])) {
$this->systemMessages->addNotice($this->t('Failed to update contact record.'));
}
}
@ -164,8 +164,22 @@ class Profile extends BaseModule
throw new HTTPException\NotFoundException($this->t('Contact not found.'));
}
// Fetch the protocol from the user's contact.
if ($data['user']) {
$usercontact = Contact::getById($data['user'], ['network', 'protocol']);
if ($this->db->isResult($usercontact)) {
$contact['network'] = $usercontact['network'];
$contact['protocol'] = $usercontact['protocol'];
}
}
if (empty($contact['network']) && Contact::isLocal($contact['url']) ) {
$contact['network'] = Protocol::DFRN;
$contact['protocol'] = Protocol::ACTIVITYPUB;
}
// Don't display contacts that are about to be deleted
if ($this->db->isResult($contact) && (!empty($contact['deleted']) || !empty($contact['network']) && $contact['network'] == Protocol::PHANTOM)) {
if ($contact['deleted'] || $contact['network'] == Protocol::PHANTOM) {
throw new HTTPException\NotFoundException($this->t('Contact not found.'));
}

View file

@ -30,6 +30,7 @@ use Friendica\Core\Renderer;
use Friendica\Database\Database;
use Friendica\DI;
use Friendica\Model;
use Friendica\Model\Contact as ModelContact;
use Friendica\Module\Contact;
use Friendica\Module\Response;
use Friendica\Module\Security\Login;
@ -58,16 +59,12 @@ class Revoke extends BaseModule
return;
}
$data = Model\Contact::getPublicAndUserContactID($this->parameters['id'], DI::userSession()->getLocalUserId());
if (!$this->dba->isResult($data)) {
throw new HTTPException\NotFoundException($this->t('Unknown contact.'));
}
if (empty($data['user'])) {
$ucid = Model\Contact::getUserContactId($this->parameters['id'], DI::userSession()->getLocalUserId());
if (!$ucid) {
throw new HTTPException\ForbiddenException();
}
$this->contact = Model\Contact::getById($data['user']);
$this->contact = Model\Contact::getById($ucid);
if ($this->contact['deleted']) {
throw new HTTPException\NotFoundException($this->t('Contact is deleted.'));
@ -90,7 +87,7 @@ class Revoke extends BaseModule
DI::sysmsg()->addNotice($this->t('Follow was successfully revoked.'));
$this->baseUrl->redirect('contact/' . $this->parameters['id']);
$this->baseUrl->redirect('contact/' . ModelContact::getPublicContactId($this->parameters['id'], DI::userSession()->getLocalUserId()));
}
protected function content(array $request = []): string

View file

@ -168,7 +168,7 @@ class Unfollow extends \Friendica\BaseModule
$this->baseUrl->redirect($base_return_path);
}
$return_path = $base_return_path . '/' . $contact['id'];
$return_path = $base_return_path . '/' . Contact::getPublicContactId($contact['id'], $uid);
try {
Contact::unfollow($contact);

View file

@ -47,10 +47,12 @@ use Friendica\Core\L10n;
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
use Friendica\Core\Renderer;
use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\Database\Database;
use Friendica\Model\Contact;
use Friendica\Model\Circle;
use Friendica\Model\Post;
use Friendica\Model\Profile;
use Friendica\Module\Response;
use Friendica\Module\Security\Login;
@ -231,7 +233,7 @@ class Network extends Timeline
} else {
$items = $this->getItems();
}
$o .= $this->conversation->render($items, Conversation::MODE_NETWORK, false, false, $this->getOrder(), $this->session->getLocalUserId());
} catch (\Exception $e) {
$o .= $this->l10n->t('Error %d (%s) while fetching the timeline.', $e->getCode(), $e->getMessage());
@ -470,23 +472,20 @@ class Network extends Timeline
$items = array_reverse($items);
}
if ($this->database->isResult($items)) {
$parents = array_column($items, 'uri-id');
} else {
$parents = [];
if ($this->ping || !$this->database->isResult($items)) {
return $items;
}
// We aren't going to try and figure out at the item, circle, and page
// level which items you've seen and which you haven't. If you're looking
// at the top level network page just mark everything seen.
if (!$this->circleId && !$this->star && !$this->mention) {
$condition = ['unseen' => true, 'uid' => $this->session->getLocalUserId()];
$this->setItemsSeenByCondition($condition);
} elseif (!empty($parents)) {
$condition = ['unseen' => true, 'uid' => $this->session->getLocalUserId(), 'parent-uri-id' => $parents];
$this->setItemsSeenByCondition($condition);
$this->setItemsSeenByCondition(['unseen' => true, 'uid' => $this->session->getLocalUserId(), 'parent-uri-id' => array_column($items, 'uri-id')]);
$posts = Post::selectToArray(['uri-id'], ['unseen' => true, 'uid' => $this->session->getLocalUserId()], ['limit' => 100]);
if (!empty($posts)) {
$this->setItemsSeenByCondition(['unseen' => true, 'uid' => $this->session->getLocalUserId(), 'uri-id' => array_column($posts, 'uri-id')]);
}
if (count($posts) == 100) {
Worker::add(Worker::PRIORITY_MEDIUM, 'SetSeen', $this->session->getLocalUserId());
}
return $items;
}

View file

@ -22,8 +22,10 @@
namespace Friendica\Module\DFRN;
use Friendica\BaseModule;
use Friendica\Core\Protocol;
use Friendica\Model\Contact;
use Friendica\Model\Conversation;
use Friendica\Model\Item;
use Friendica\Model\User;
use Friendica\Module\Response;
use Friendica\Network\HTTPException;
@ -45,6 +47,8 @@ class Notify extends BaseModule
throw new HTTPException\BadRequestException();
}
Item::incrementInbound(Protocol::DFRN);
$data = json_decode($postdata);
if (is_object($data) && !empty($this->parameters['nickname'])) {
$user = User::getByNickname($this->parameters['nickname']);

View file

@ -29,6 +29,12 @@ use Friendica\Util\JsonLD;
class ActivityPubConversion extends BaseModule
{
protected function post(array $request = [])
{
// @todo check if POST is really used here
$this->content($request);
}
protected function content(array $request = []): string
{
function visible_whitespace($s)

View file

@ -35,6 +35,12 @@ use Friendica\Util\XML;
*/
class Babel extends BaseModule
{
protected function post(array $request = [])
{
// @todo check if POST is really used here
$this->content($request);
}
protected function content(array $request = []): string
{
function visible_whitespace($s)

View file

@ -25,6 +25,8 @@ use Friendica\App;
use Friendica\BaseModule;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\L10n;
use Friendica\Core\Protocol;
use Friendica\Model\Item;
use Friendica\Model\User;
use Friendica\Module\Response;
use Friendica\Network\HTTPException;
@ -57,6 +59,8 @@ class Receive extends BaseModule
throw new HTTPException\ForbiddenException($this->t('Access denied.'));
}
Item::incrementInbound(Protocol::DIASPORA);
if ($this->parameters['type'] === 'public') {
$this->receivePublic();
} else if ($this->parameters['type'] === 'users') {

View file

@ -22,6 +22,8 @@
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Core\Protocol;
use Friendica\Model\Item;
use Friendica\Model\User;
use Friendica\Network\HTTPException;
use Friendica\Protocol\Feed as ProtocolFeed;
@ -68,6 +70,8 @@ class Feed extends BaseModule
throw new HTTPException\UnauthorizedException($this->t('Access to this profile has been restricted.'));
}
Item::incrementOutbound(Protocol::FEED);
$feed = ProtocolFeed::atom($owner, $last_update, 10, $type);
$this->httpExit($feed, Response::TYPE_ATOM);

View file

@ -36,7 +36,7 @@ use Friendica\Protocol\Diaspora;
*/
class Activity extends BaseModule
{
protected function rawContent(array $request = [])
protected function post(array $request = [])
{
if (!DI::userSession()->isAuthenticated()) {
throw new HTTPException\ForbiddenException();

View file

@ -33,7 +33,7 @@ use Friendica\Network\HTTPException;
*/
class Follow extends BaseModule
{
protected function rawContent(array $request = [])
protected function post(array $request = [])
{
$l10n = DI::l10n();

View file

@ -33,7 +33,7 @@ use Friendica\Network\HTTPException;
*/
class Ignore extends BaseModule
{
protected function rawContent(array $request = [])
protected function post(array $request = [])
{
$l10n = DI::l10n();

View file

@ -33,7 +33,7 @@ use Friendica\Network\HTTPException;
*/
class Pin extends BaseModule
{
protected function rawContent(array $request = [])
protected function post(array $request = [])
{
$l10n = DI::l10n();

View file

@ -34,7 +34,7 @@ use Friendica\Network\HTTPException;
*/
class Star extends BaseModule
{
protected function rawContent(array $request = [])
protected function post(array $request = [])
{
$l10n = DI::l10n();

View file

@ -106,7 +106,7 @@ class Upload extends \Friendica\BaseModule
$this->return(401, $msg);
}
$newid = Attach::storeFile($tempFileName, $owner['uid'], $fileName, '<' . $owner['id'] . '>');
$newid = Attach::storeFile($tempFileName, $owner['uid'], $fileName, $_FILES['userfile']['type'] ?? '', '<' . $owner['id'] . '>');
@unlink($tempFileName);

View file

@ -45,6 +45,12 @@ class Source extends BaseModeration
$this->config = $config;
}
protected function post(array $request = [])
{
// @todo check if POST is really used here
$this->content($request);
}
protected function content(array $request = []): string
{
parent::content();

View file

@ -48,6 +48,12 @@ class Reports extends BaseModeration
$this->database = $database;
}
protected function post(array $request = [])
{
// @todo check if POST is really used here
$this->content($request);
}
protected function content(array $request = []): string
{
parent::content();

View file

@ -56,10 +56,11 @@ class NodeInfo120 extends BaseModule
],
'protocols' => ['dfrn', 'activitypub'],
'services' => Nodeinfo::getServices(),
'usage' => Nodeinfo::getUsage(),
'openRegistrations' => Register::getPolicy() !== Register::CLOSED,
'usage' => Nodeinfo::getUsage(),
'metadata' => [
'nodeName' => $this->config->get('config', 'sitename'),
'nodeName' => $this->config->get('config', 'sitename'),
'nodeDescription' => $this->config->get('config', 'info'),
],
];

View file

@ -0,0 +1,90 @@
<?php
/**
* @copyright Copyright (C) 2010-2024, 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/>.
*
*/
namespace Friendica\Module;
use Friendica\App;
use Friendica\BaseModule;
use Friendica\Capabilities\ICanCreateResponses;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\L10n;
use Friendica\Model\Nodeinfo;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
/**
* Version 2.1 of Nodeinfo, a standardized way of exposing metadata about a server running one of the distributed social networks.
* @see https://github.com/jhass/nodeinfo/blob/master/PROTOCOL.md
*/
class NodeInfo121 extends BaseModule
{
/** @var IManageConfigValues */
protected $config;
public function __construct(L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IManageConfigValues $config, array $server, array $parameters = [])
{
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->config = $config;
}
protected function rawContent(array $request = [])
{
$nodeinfo = [
'version' => '2.1',
'software' => [
'name' => 'friendica',
'version' => App::VERSION . '-' . DB_UPDATE_VERSION,
'repository' => 'https://github.com/friendica/friendica',
'homepage' => 'https://friendi.ca',
],
'protocols' => ['dfrn', 'activitypub'],
'services' => Nodeinfo::getServices(),
'openRegistrations' => Register::getPolicy() !== Register::CLOSED,
'usage' => Nodeinfo::getUsage(),
'metadata' => [
'nodeName' => $this->config->get('config', 'sitename'),
'nodeDescription' => $this->config->get('config', 'info'),
],
];
if (!empty($this->config->get('system', 'diaspora_enabled'))) {
$nodeinfo['protocols'][] = 'diaspora';
}
if (empty($this->config->get('system', 'ostatus_disabled'))) {
$nodeinfo['protocols'][] = 'ostatus';
}
$nodeinfo['services']['inbound'][] = 'atom1.0';
$nodeinfo['services']['inbound'][] = 'rss2.0';
$nodeinfo['services']['outbound'][] = 'atom1.0';
if (function_exists('imap_open') && !$this->config->get('system', 'imap_disabled')) {
$nodeinfo['services']['inbound'][] = 'imap';
}
$nodeinfo['metadata']['explicitContent'] = $this->config->get('system', 'explicit_content', false) == true;
$this->response->setType(ICanCreateResponses::TYPE_JSON, 'application/json; charset=utf-8');
$this->response->addContent(json_encode($nodeinfo, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
}

View file

@ -0,0 +1,91 @@
<?php
/**
* @copyright Copyright (C) 2010-2024, 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/>.
*
*/
namespace Friendica\Module;
use Friendica\App;
use Friendica\BaseModule;
use Friendica\Capabilities\ICanCreateResponses;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\L10n;
use Friendica\Model\Nodeinfo;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
/**
* Version 2.2 of Nodeinfo, a standardized way of exposing metadata about a server running one of the distributed social networks.
* @see https://github.com/jhass/nodeinfo/blob/master/PROTOCOL.md
*/
class NodeInfo122 extends BaseModule
{
/** @var IManageConfigValues */
protected $config;
public function __construct(L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IManageConfigValues $config, array $server, array $parameters = [])
{
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->config = $config;
}
protected function rawContent(array $request = [])
{
$nodeinfo = [
'version' => '2.2',
'instance' => [
'name' => $this->config->get('config', 'sitename'),
'description' => $this->config->get('config', 'info'),
],
'software' => [
'name' => 'friendica',
'version' => App::VERSION . '-' . DB_UPDATE_VERSION,
'repository' => 'https://github.com/friendica/friendica',
'homepage' => 'https://friendi.ca',
],
'protocols' => ['dfrn', 'activitypub'],
'services' => Nodeinfo::getServices(),
'openRegistrations' => Register::getPolicy() !== Register::CLOSED,
'usage' => Nodeinfo::getUsage(),
'metadata' => [],
];
if (!empty($this->config->get('system', 'diaspora_enabled'))) {
$nodeinfo['protocols'][] = 'diaspora';
}
if (empty($this->config->get('system', 'ostatus_disabled'))) {
$nodeinfo['protocols'][] = 'ostatus';
}
$nodeinfo['services']['inbound'][] = 'atom1.0';
$nodeinfo['services']['inbound'][] = 'rss2.0';
$nodeinfo['services']['outbound'][] = 'atom1.0';
if (function_exists('imap_open') && !$this->config->get('system', 'imap_disabled')) {
$nodeinfo['services']['inbound'][] = 'imap';
}
$nodeinfo['metadata']['explicitContent'] = $this->config->get('system', 'explicit_content', false) == true;
$this->response->setType(ICanCreateResponses::TYPE_JSON, 'application/json; charset=utf-8');
$this->response->addContent(json_encode($nodeinfo, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
}

View file

@ -32,7 +32,7 @@ use Psr\Log\LoggerInterface;
/**
* Version 1.0 of Nodeinfo 2, a sStandardized way of exposing metadata about a server running one of the distributed social networks.
* @see https://github.com/jhass/nodeinfo/blob/master/PROTOCOL.md
* @see https://github.com/jaywink/nodeinfo2/blob/master/PROTOCOL.md
*/
class NodeInfo210 extends BaseModule
{

View file

@ -77,6 +77,12 @@ class Introductions extends BaseNotifications
];
}
protected function post(array $request = [])
{
// @todo check if POST is really used here
$this->content($request);
}
protected function content(array $request = []): string
{
Nav::setSelected('introductions');

View file

@ -96,6 +96,12 @@ class Notifications extends BaseNotifications
];
}
protected function post(array $request = [])
{
// @todo check if POST is really used here
$this->content($request);
}
protected function content(array $request = []): string
{
Nav::setSelected('notifications');

View file

@ -45,7 +45,7 @@ class Acknowledge extends BaseApi
protected function content(array $request = []): string
{
DI::session()->set('return_path', $_REQUEST['return_path'] ?? '');
DI::session()->set('return_path', 'oauth/authorize?' . $request['return_authorize']);
$o = Renderer::replaceMacros(Renderer::getMarkupTemplate('oauth_authorize.tpl'), [
'$title' => DI::l10n()->t('Authorize application connection'),

View file

@ -68,19 +68,19 @@ class Authorize extends BaseApi
$redirect_request = $_REQUEST;
unset($redirect_request['pagename']);
$redirect = 'oauth/authorize?' . http_build_query($redirect_request);
$redirect = http_build_query($redirect_request);
$uid = DI::userSession()->getLocalUserId();
if (empty($uid)) {
Logger::info('Redirect to login');
DI::app()->redirect('login?return_path=' . urlencode($redirect));
DI::app()->redirect('login?' . http_build_query(['return_authorize' => $redirect]));
} else {
Logger::info('Already logged in user', ['uid' => $uid]);
}
if (!OAuth::existsTokenForUser($application, $uid) && !DI::session()->get('oauth_acknowledge')) {
Logger::info('Redirect to acknowledge');
DI::app()->redirect('oauth/acknowledge?' . http_build_query(['return_path' => $redirect, 'application' => $application['name']]));
DI::app()->redirect('oauth/acknowledge?' . http_build_query(['return_authorize' => $redirect, 'application' => $application['name']]));
}
DI::session()->remove('oauth_acknowledge');

View file

@ -28,6 +28,7 @@ use Friendica\Core\System;
use Friendica\Database\Database;
use Friendica\Model\Contact;
use Friendica\Model\GServer;
use Friendica\Model\Item;
use Friendica\Model\Post;
use Friendica\Module\Response;
use Friendica\Network\HTTPException;
@ -101,6 +102,7 @@ class PubSub extends \Friendica\BaseModule
$this->logger->info('Import item from Contact.', ['nickname' => $nickname, 'contact-nickname' => $contact['nick'], 'contact-id' => $contact['id']]);
$feedhub = '';
Item::incrementOutbound(Protocol::OSTATUS);
OStatus::import($xml, $importer, $contact, $feedhub);
throw new HTTPException\OKException();

View file

@ -26,6 +26,7 @@ use Friendica\Core\L10n;
use Friendica\Core\Protocol;
use Friendica\Database\Database;
use Friendica\Model\GServer;
use Friendica\Model\Item;
use Friendica\Model\Post;
use Friendica\Module\Response;
use Friendica\Protocol\ActivityNamespace;
@ -210,6 +211,7 @@ class Salmon extends \Friendica\BaseModule
$contact = $contact ?: [];
Item::incrementOutbound(Protocol::OSTATUS);
OStatus::import($data, $importer, $contact, $hub);
throw new HTTPException\OKException();

View file

@ -68,7 +68,7 @@ class Remove extends \Friendica\BaseModule
protected function content(array $request = []): string
{
$returnUrl = $request['return'] ?? '';
$returnUrl = hex2bin($request['return'] ?? '');
if (!$this->session->getLocalUserId()) {
$this->baseUrl->redirect($returnUrl);

View file

@ -64,7 +64,7 @@ class Media extends BaseProfile
$o = self::getTabsHTML('media', $is_owner, $profile['nickname'], $profile['hide-friends']);
$o .= Contact::getPostsFromUrl($profile['url'], $this->userSession->getLocalUserId(), true);
$o .= Contact::getPostsFromUrl($profile['url'], $this->userSession->getLocalUserId(), true, $request['last_created'] ?? '');
return $o;
}

View file

@ -26,13 +26,13 @@ use Friendica\Content\Feature;
use Friendica\Content\GroupManager;
use Friendica\Content\Nav;
use Friendica\Content\Text\BBCode;
use Friendica\Content\Text\HTML;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Hook;
use Friendica\Core\L10n;
use Friendica\Core\Protocol;
use Friendica\Core\Renderer;
use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Core\System;
use Friendica\Database\Database;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
@ -43,12 +43,14 @@ use Friendica\Module\BaseProfile;
use Friendica\Module\Response;
use Friendica\Module\Security\Login;
use Friendica\Network\HTTPException;
use Friendica\Network\HTTPException\InternalServerErrorException;
use Friendica\Profile\ProfileField\Repository\ProfileField;
use Friendica\Protocol\ActivityPub;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Network;
use Friendica\Util\Profiler;
use Friendica\Util\Strings;
use Friendica\Util\Temporal;
use GuzzleHttp\Psr7\Uri;
use Psr\Log\LoggerInterface;
class Profile extends BaseProfile
@ -164,7 +166,7 @@ class Profile extends BaseProfile
$basic_fields = [];
$basic_fields += self::buildField('fullname', $this->t('Full Name:'), $profile['name']);
$basic_fields += self::buildField('fullname', $this->t('Full Name:'), $this->cleanInput($profile['uri-id'], $profile['name']));
if (Feature::isEnabled($profile['uid'], Feature::MEMBER_SINCE)) {
$basic_fields += self::buildField(
@ -196,18 +198,18 @@ class Profile extends BaseProfile
}
if ($profile['xmpp']) {
$basic_fields += self::buildField('xmpp', $this->t('XMPP:'), $profile['xmpp']);
$basic_fields += self::buildField('xmpp', $this->t('XMPP:'), $this->cleanInput($profile['uri-id'], $profile['xmpp']));
}
if ($profile['matrix']) {
$basic_fields += self::buildField('matrix', $this->t('Matrix:'), $profile['matrix']);
$basic_fields += self::buildField('matrix', $this->t('Matrix:'), $this->cleanInput($profile['uri-id'], $profile['matrix']));
}
if ($profile['homepage']) {
$basic_fields += self::buildField(
'homepage',
$this->t('Homepage:'),
$this->tryRelMe($profile['homepage']) ?: $profile['homepage']
$this->tryRelMe($profile['homepage']) ?: $this->cleanInput($profile['uri-id'], $profile['homepage'])
);
}
@ -218,7 +220,7 @@ class Profile extends BaseProfile
|| $profile['region']
|| $profile['country-name']
) {
$basic_fields += self::buildField('location', $this->t('Location:'), ProfileModel::formatLocation($profile));
$basic_fields += self::buildField('location', $this->t('Location:'), $this->cleanInput($profile['uri-id'], ProfileModel::formatLocation($profile)));
}
if ($profile['pub_keywords']) {
@ -372,10 +374,28 @@ class Profile extends BaseProfile
*/
private function tryRelMe(string $input): string
{
if (preg_match(Strings::onlyLinkRegEx(), trim($input))) {
return '<a href="' . trim($input) . '" target="_blank" rel="noopener noreferrer me">' . trim($input) . '</a>';
$input = trim($input);
if (Network::isValidHttpUrl($input)) {
try {
$input = (string)Uri::fromParts(parse_url($input));
return '<a href="' . $input . '" target="_blank" rel="noopener noreferrer me">' . $input . '</a>';
} catch (\Throwable $th) {
return '';
}
}
return '';
}
/**
* Clean the provided input to prevent XSS problems
* @param int $uri_id
* @param string $input
* @return string
* @throws InternalServerErrorException
*/
private function cleanInput(int $uri_id, string $input): string
{
return BBCode::convertForUriId($uri_id, HTML::toBBCode($input));
}
}

View file

@ -67,6 +67,11 @@ class Acl extends BaseModule
$this->database = $database;
}
protected function post(array $request = [])
{
$this->rawContent($request);
}
protected function rawContent(array $request = [])
{
if (!$this->session->getLocalUserId()) {

View file

@ -48,7 +48,11 @@ class Saved extends BaseModule
$action = $this->args->get(2, 'none');
$search = trim(rawurldecode($_GET['term'] ?? ''));
$return_url = $_GET['return_url'] ?? Search::getSearchPath($search);
if (!empty($_GET['return_url'])) {
$return_url = hex2bin($_GET['return_url']);
} else {
$return_url = Search::getSearchPath($search);
}
if (DI::userSession()->getLocalUserId() && $search) {
switch ($action) {

View file

@ -49,7 +49,7 @@ class Tags extends BaseModule
$this->database = $database;
}
protected function rawContent(array $request = [])
protected function post(array $request = [])
{
$tags = $request['s'] ?? '';
$perPage = intval($request['n'] ?? self::DEFAULT_ITEMS_PER_PAGE);

View file

@ -60,7 +60,11 @@ class Login extends BaseModule
protected function content(array $request = []): string
{
$return_path = $request['return_path'] ?? $this->session->pop('return_path', '') ;
if (!empty($request['return_authorize'])) {
$return_path = 'oauth/authorize?' . $request['return_authorize'];
} else {
$return_path = $request['return_path'] ?? $this->session->pop('return_path', '') ;
}
if ($this->session->getLocalUserId()) {
$this->baseUrl->redirect($return_path);

View file

@ -56,6 +56,11 @@ class Logout extends BaseModule
$this->session = $session;
}
protected function post(array $request = [])
{
// @todo check if POST is really used here
$this->rawContent($request);
}
/**
* Process logout requests

View file

@ -330,6 +330,11 @@ class Account extends BaseSettings
}
User::setCommunityUserSettings(DI::userSession()->getLocalUserId());
if ($account_type == User::ACCOUNT_TYPE_RELAY) {
Profile::setResponsibleRelayContact(DI::userSession()->getLocalUserId());
}
DI::baseUrl()->redirect($redirectUrl);
}
@ -425,7 +430,7 @@ class Account extends BaseSettings
$user['account-type'] = User::ACCOUNT_TYPE_COMMUNITY;
}
if (DI::config()->get('system', 'allow_relay_channels')) {
if (!empty($user['parent-uid']) && DI::config()->get('system', 'allow_relay_channels')) {
$account_relay = [
'account-type',
DI::l10n()->t('Channel Relay'),

View file

@ -125,18 +125,21 @@ class Index extends BaseSettings
$country_name = trim($request['country_name']);
$pub_keywords = self::cleanKeywords(trim($request['pub_keywords']));
$prv_keywords = self::cleanKeywords(trim($request['prv_keywords']));
$xmpp = trim($request['xmpp']);
$matrix = trim($request['matrix']);
$homepage = trim($request['homepage']);
$xmpp = $this->cleanInput(trim($request['xmpp']));
$matrix = $this->cleanInput(trim($request['matrix']));
$homepage = $this->cleanInput(trim($request['homepage']));
if ((strpos($homepage, 'http') !== 0) && (strlen($homepage))) {
// neither http nor https in URL, add them
$homepage = 'http://' . $homepage;
}
$user = User::getById($this->session->getLocalUserId());
$about = Profile::addResponsibleRelayContact($about, $user['parent-uid'], $user['account-type'], $user['language']);
$profileFieldsNew = $this->getProfileFieldsFromInput(
$this->session->getLocalUserId(),
$request['profile_field'],
$request['profile_field_order']
(array)$request['profile_field'],
(array)$request['profile_field_order']
);
$this->profileFieldRepo->saveCollectionForUser($this->session->getLocalUserId(), $profileFieldsNew);
@ -187,6 +190,8 @@ class Index extends BaseSettings
throw new HTTPException\NotFoundException();
}
$owner['about'] = Profile::addResponsibleRelayContact($owner['about'], $owner['parent-uid'], $owner['account-type'], $owner['language']);
$this->page->registerFooterScript('view/asset/es-jquery-sortable/source/js/jquery-sortable-min.js');
$this->page->registerFooterScript(Theme::getPathForFile('js/module/settings/profile/index.js'));
@ -353,6 +358,11 @@ class Index extends BaseSettings
return $profileFields;
}
private function cleanInput(string $input): string
{
return str_replace(['<', '>', '"', ' '], '', $input);
}
private static function cleanKeywords($keywords): string
{
$keywords = str_replace(',', ' ', $keywords);

205
src/Module/Stats.php Normal file
View file

@ -0,0 +1,205 @@
<?php
/**
* @copyright Copyright (C) 2010-2024, 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/>.
*
*/
namespace Friendica\Module;
use Friendica\App;
use Friendica\BaseModule;
use Friendica\Core\Addon;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\KeyValueStorage\Capability\IManageKeyValuePairs;
use Friendica\Core\L10n;
use Friendica\Core\Protocol;
use Friendica\Core\Worker;
use Friendica\Database\Database;
use Friendica\Model\Register;
use Friendica\Moderation\Entity\Report;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
use Friendica\Network\HTTPException;
class Stats extends BaseModule
{
/** @var IManageConfigValues */
protected $config;
/** @var Database */
protected $dba;
/** @var LoggerInterface */
protected $logger;
/** @var IManageKeyValuePairs */
protected $keyValue;
public function __construct(L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, IManageConfigValues $config, IManageKeyValuePairs $keyValue, Database $dba, Response $response, array $server, array $parameters = [])
{
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->config = $config;
$this->keyValue = $keyValue;
$this->dba = $dba;
}
protected function content(array $request = []): string
{
if (!$this->isAllowed($request)) {
throw new HTTPException\NotFoundException($this->l10n->t('Page not found.'));
}
return '';
}
protected function rawContent(array $request = [])
{
if (!$this->isAllowed($request)) {
return;
}
$report = $this->dba->selectFirst('report', ['created'], [], ['order' => ['created' => true]]);
if (!empty($report)) {
$report_datetime = DateTimeFormat::utc($report['created'], DateTimeFormat::JSON);
$report_timestamp = strtotime($report['created']);
} else {
$report_datetime = '';
$report_timestamp = 0;
}
$statistics = [
'cron' => [
'lastExecution' => [
'datetime' => date(DateTimeFormat::JSON, (int)$this->keyValue->get('last_cron')),
'timestamp' => (int)$this->keyValue->get('last_cron'),
],
],
'worker' => [
'lastExecution' => [
'datetime' => DateTimeFormat::utc($this->keyValue->get('last_worker_execution'), DateTimeFormat::JSON),
'timestamp' => strtotime($this->keyValue->get('last_worker_execution')),
],
'jpm' => [
1 => $this->dba->count('workerqueue', ["`done` AND `executed` > ?", DateTimeFormat::utc('now - 1 minute')]),
3 => round($this->dba->count('workerqueue', ["`done` AND `executed` > ?", DateTimeFormat::utc('now - 3 minute')]) / 3),
5 => round($this->dba->count('workerqueue', ["`done` AND `executed` > ?", DateTimeFormat::utc('now - 5 minute')]) / 5),
],
'active' => [],
'deferred' => [],
'total' => [],
],
'users' => [
'total' => intval($this->keyValue->get('nodeinfo_total_users')),
'activeWeek' => intval($this->keyValue->get('nodeinfo_active_users_weekly')),
'activeMonth' => intval($this->keyValue->get('nodeinfo_active_users_monthly')),
'activeHalfyear' => intval($this->keyValue->get('nodeinfo_active_users_halfyear')),
'pending' => Register::getPendingCount(),
],
'posts' => [
'inbound' => [
'posts' => intval($this->keyValue->get('nodeinfo_total_posts')) - intval($this->keyValue->get('nodeinfo_local_posts')),
'comments' => intval($this->keyValue->get('nodeinfo_total_comments')) - intval($this->keyValue->get('nodeinfo_local_comments')),
],
'outbound' => [
'posts' => intval($this->keyValue->get('nodeinfo_local_posts')),
'comments' => intval($this->keyValue->get('nodeinfo_local_comments')),
],
],
'packets' => [
'inbound' => [
Protocol::ACTIVITYPUB => intval($this->keyValue->get('stats_packets_inbound_' . Protocol::ACTIVITYPUB) ?? 0),
Protocol::DFRN => intval($this->keyValue->get('stats_packets_inbound_' . Protocol::DFRN) ?? 0),
Protocol::DIASPORA => intval($this->keyValue->get('stats_packets_inbound_' . Protocol::DIASPORA) ?? 0),
Protocol::OSTATUS => intval($this->keyValue->get('stats_packets_inbound_' . Protocol::OSTATUS) ?? 0),
Protocol::FEED => intval($this->keyValue->get('stats_packets_inbound_' . Protocol::FEED) ?? 0),
Protocol::MAIL => intval($this->keyValue->get('stats_packets_inbound_' . Protocol::MAIL) ?? 0),
],
'outbound' => [
Protocol::ACTIVITYPUB => intval($this->keyValue->get('stats_packets_outbound_' . Protocol::ACTIVITYPUB) ?? 0),
Protocol::DFRN => intval($this->keyValue->get('stats_packets_outbound_' . Protocol::DFRN) ?? 0),
Protocol::DIASPORA => intval($this->keyValue->get('stats_packets_outbound_' . Protocol::DIASPORA) ?? 0),
Protocol::OSTATUS => intval($this->keyValue->get('stats_packets_outbound_' . Protocol::OSTATUS) ?? 0),
Protocol::FEED => intval($this->keyValue->get('stats_packets_outbound_' . Protocol::FEED) ?? 0),
Protocol::MAIL => intval($this->keyValue->get('stats_packets_outbound_' . Protocol::MAIL) ?? 0),
]
],
'reports' => [
'newest' => [
'datetime' => $report_datetime,
'timestamp' => $report_timestamp,
],
'open' => $this->dba->count('report', ['status' => Report::STATUS_OPEN]),
'closed' => $this->dba->count('report', ['status' => Report::STATUS_CLOSED]),
]
];
if (Addon::isEnabled('bluesky')) {
$statistics['packets']['inbound'][Protocol::BLUESKY] = intval($this->keyValue->get('stats_packets_inbound_' . Protocol::BLUESKY) ?? 0);
$statistics['packets']['outbound'][Protocol::BLUESKY] = intval($this->keyValue->get('stats_packets_outbound_' . Protocol::BLUESKY) ?? 0);
}
if (Addon::isEnabled('tumblr')) {
$statistics['packets']['inbound'][Protocol::TUMBLR] = intval($this->keyValue->get('stats_packets_inbound_' . Protocol::TUMBLR) ?? 0);
$statistics['packets']['outbound'][Protocol::TUMBLR] = intval($this->keyValue->get('stats_packets_outbound_' . Protocol::TUMBLR) ?? 0);
}
$statistics = $this->getJobsPerPriority($statistics);
$this->jsonExit($statistics);
}
private function isAllowed(array $request): bool
{
return empty(!$request['key']) && $request['key'] == $this->config->get('system', 'stats_key');
}
private function getJobsPerPriority(array $statistics): array
{
$statistics['worker']['active'] = $statistics['worker']['total'] = [
Worker::PRIORITY_UNDEFINED => 0,
Worker::PRIORITY_CRITICAL => 0,
Worker::PRIORITY_HIGH => 0,
Worker::PRIORITY_MEDIUM => 0,
Worker::PRIORITY_LOW => 0,
Worker::PRIORITY_NEGLIGIBLE => 0,
'total' => 0,
];
for ($i = 1; $i <= $this->config->get('system', 'worker_defer_limit'); $i++) {
$statistics['worker']['deferred'][$i] = 0;
}
$statistics['worker']['deferred']['total'] = 0;
$jobs = $this->dba->p("SELECT COUNT(*) AS `entries`, `priority` FROM `workerqueue` WHERE NOT `done` AND `retrial` = ? GROUP BY `priority`", 0);
while ($entry = $this->dba->fetch($jobs)) {
$running = $this->dba->count('workerqueue-view', ['priority' => $entry['priority']]);
$statistics['worker']['active']['total'] += $running;
$statistics['worker']['active'][$entry['priority']] = $running;
$statistics['worker']['total']['total'] += $entry['entries'];
$statistics['worker']['total'][$entry['priority']] = $entry['entries'];
}
$this->dba->close($jobs);
$statistics['worker']['active'][Worker::PRIORITY_UNDEFINED] = max(0, Worker::activeWorkers() - $statistics['worker']['active']['total']);
$jobs = $this->dba->p("SELECT COUNT(*) AS `entries`, `retrial` FROM `workerqueue` WHERE NOT `done` AND `retrial` > ? GROUP BY `retrial`", 0);
while ($entry = $this->dba->fetch($jobs)) {
$statistics['worker']['deferred']['total'] += $entry['entries'];
$statistics['worker']['deferred'][$entry['retrial']] = $entry['entries'];
}
$this->dba->close($jobs);
return $statistics;
}
}

View file

@ -22,7 +22,6 @@
namespace Friendica\Module\WellKnown;
use Friendica\BaseModule;
use Friendica\Core\System;
use Friendica\DI;
/**
@ -35,10 +34,22 @@ class NodeInfo extends BaseModule
{
$nodeinfo = [
'links' => [
['rel' => 'http://nodeinfo.diaspora.software/ns/schema/1.0',
'href' => DI::baseUrl() . '/nodeinfo/1.0'],
['rel' => 'http://nodeinfo.diaspora.software/ns/schema/2.0',
'href' => DI::baseUrl() . '/nodeinfo/2.0'],
[
'rel' => 'http://nodeinfo.diaspora.software/ns/schema/1.0',
'href' => DI::baseUrl() . '/nodeinfo/1.0'
],
[
'rel' => 'http://nodeinfo.diaspora.software/ns/schema/2.0',
'href' => DI::baseUrl() . '/nodeinfo/2.0'
],
[
'rel' => 'http://nodeinfo.diaspora.software/ns/schema/2.1',
'href' => DI::baseUrl() . '/nodeinfo/2.1'
],
[
'rel' => 'http://nodeinfo.diaspora.software/ns/schema/2.2',
'href' => DI::baseUrl() . '/nodeinfo/2.2'
],
]
];