Merge remote-tracking branch 'upstream/develop' into update

This commit is contained in:
Michael 2021-05-14 06:37:24 +00:00
commit 4d0e6305dd
148 changed files with 25160 additions and 21008 deletions

63
.drone.yml Normal file
View File

@ -0,0 +1,63 @@
kind: pipeline
type: docker
name: Check messages.po
steps:
- name: Run default Xgettext
image: friendicaci/transifex
commands:
- ./bin/run_xgettext.sh
- name: Check default
image: friendicaci/transifex
commands:
- /check-messages.sh
---
kind: pipeline
type: docker
name: php7.3-lint
steps:
- name: Test
image: php:7.3
commands:
- ./bin/composer.phar run lint
---
kind: pipeline
type: docker
name: php7.4-lint
steps:
- name: Test
image: php:7.4
commands:
- ./bin/composer.phar run lint
---
kind: pipeline
type: docker
name: php8.0-lint
steps:
- name: Test
image: php:8.0
commands:
- ./bin/composer.phar run lint
---
kind: pipeline
type: docker
name: php-cs check
trigger:
event:
- pull_request
steps:
- name: Install dependencies
image: composer
commands:
- ./bin/composer.phar run cs:install
- name: Run coding standards check
image: friendicaci/php-cs
commands:
- export CHANGED_FILES="$(git diff --name-status ${DRONE_COMMIT_BEFORE}..${DRONE_COMMIT_AFTER} | grep ^A | cut -f2)"
- /check-php-cs.sh

View File

@ -1,42 +0,0 @@
name: Lint
on: pull_request
jobs:
php-linters:
name: php${{ matrix.php-versions }} lint
runs-on: ubuntu-latest
strategy:
matrix:
php-versions: ['7.3', '7.4', '8.0']
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up php${{ matrix.php-versions }}
uses: shivammathur/setup-php@master
with:
php-version: ${{ matrix.php-versions }}
coverage: none
- name: Lint
run: bin/composer.phar run lint
php-cs-fixer:
name: php-cs check
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Get changed files
id: changes
uses: jitterbit/get-changed-files@v1
- name: Set up php
uses: shivammathur/setup-php@master
with:
php-version: 7.2
coverage: none
- name: Install dependencies
run: bin/composer.phar run cs:install
- name: Run coding standards check
run: |
if ! echo "${{ steps.changes.outputs.added }}" | grep -qE "^(\\.php_cs(\\.dist)?|composer\\.lock)$"; then EXTRA_ARGS=$(printf -- '--path-mode=intersection\n--\n%s' "${{ steps.changes.outputs.added }}"); else EXTRA_ARGS=''; fi
bin/dev/php-cs-fixer/vendor/bin/php-cs-fixer fix --config=.php_cs.dist -v --diff --diff-format=udiff --dry-run --stop-on-violation --using-cache=no ${EXTRA_ARGS}
shell: bash

67
.github/workflows/releases.yml vendored Normal file
View File

@ -0,0 +1,67 @@
name: Generate release packages
on:
release:
types: [published]
jobs:
release:
name: Create release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2
with:
tools: pecl, composer:v1
- name: Retrieve version
id: release
run: echo "::set-output name=version::$(cat VERSION)"
- name: Validate composer.json and composer.lock
run: composer validate
- name: Get composer cache directory
id: composercache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composercache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies
run: composer install --no-dev --optimize-autoloader
- name: Create artifact
id: artifact
run: |
ARTIFACT="friendica-full-${{ steps.release.outputs.version }}.tar.gz"
echo "::set-output name=name::${ARTIFACT}"
mkdir build
tar -cvjf build/${ARTIFACT} -T mods/release-list.txt
- name: SHA256
id: sha256
run: |
cd build
ARTIFACT=${{ steps.artifact.outputs.name }}
sha256sum "${ARTIFACT}" > "${ARTIFACT}.sha256"
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: friendica
path: build/
- name: Upload to release
uses: softprops/action-gh-release@v1
with:
files: |
build/${{ steps.artifact.outputs.name }}.tar.gz
build/${{ steps.artifact.outputs.name }}.tar.gz.sha256
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,30 +0,0 @@
name: Transifex
on: [push, pull_request]
jobs:
messages:
name: Check messages.po changes
runs-on: ubuntu-latest
steps:
- name: Install gettext
run: sudo apt-get install gettext
- name: Checkout
uses: actions/checkout@v2
- name: Run Xgettext
run: ./bin/run_xgettext.sh
- name: Check if messages.po needs an update
run: |
echo "::group::Check messages.po"
# Skip first 4 lines in possible diff, because they're header
# Skip all lines of the git diff starting with "@@" or comments or starting "POT-Creation-Date"
if [[ $(git diff -U0 ./view/lang/C/messages.po | awk '!/@@|-"POT-Creation-Date|+"POT-Creation-Date|-#|+#/{print }' | wc -l) > 4 ]]; then
echo "::error file=messages.po::messages.po is out of date"
exit 1
else
echo "Nothing to update"
fi
echo "::endgroup::"
shell: bash

View File

@ -61,7 +61,7 @@ KEYWORDS="-k -kt -ktt:1,2"
echo "Extract strings to $OUTFILE.."
rm "$OUTFILE"; touch "$OUTFILE"
find_result=$(find "$FINDSTARTDIR" $FINDOPTS -name "*.php" -type f | sort)
find_result=$(find "$FINDSTARTDIR" $FINDOPTS -name "*.php" -type f | LC_ALL=C sort --stable)
total_files=$(wc -l <<< "${find_result}")

10
composer.lock generated
View File

@ -889,16 +889,16 @@
},
{
"name": "level-2/dice",
"version": "4.0.2",
"version": "4.0.3",
"source": {
"type": "git",
"url": "https://github.com/Level-2/Dice.git",
"reference": "b9336d9200d0165c31e982374dc5d8d2552807bc"
"reference": "3e9a8548398c01e2527110c916a93f6efa17ac9c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Level-2/Dice/zipball/b9336d9200d0165c31e982374dc5d8d2552807bc",
"reference": "b9336d9200d0165c31e982374dc5d8d2552807bc",
"url": "https://api.github.com/repos/Level-2/Dice/zipball/3e9a8548398c01e2527110c916a93f6efa17ac9c",
"reference": "3e9a8548398c01e2527110c916a93f6efa17ac9c",
"shasum": ""
},
"require": {
@ -931,7 +931,7 @@
"di",
"ioc"
],
"time": "2020-01-28T13:47:49+00:00"
"time": "2021-04-20T14:06:06+00:00"
},
{
"name": "lightopenid/lightopenid",

View File

@ -1,6 +1,6 @@
-- ------------------------------------------
-- Friendica 2021.06-dev (Siberian Iris)
-- DB_UPDATE_VERSION 1414
-- DB_UPDATE_VERSION 1417
-- ------------------------------------------
@ -364,6 +364,43 @@ CREATE TABLE IF NOT EXISTS `apcontact` (
FOREIGN KEY (`gsid`) REFERENCES `gserver` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='ActivityPub compatible contacts - used in the ActivityPub implementation';
--
-- TABLE application
--
CREATE TABLE IF NOT EXISTS `application` (
`id` int unsigned NOT NULL auto_increment COMMENT 'generated index',
`client_id` varchar(64) NOT NULL COMMENT '',
`client_secret` varchar(64) NOT NULL COMMENT '',
`name` varchar(255) NOT NULL COMMENT '',
`redirect_uri` varchar(255) NOT NULL COMMENT '',
`website` varchar(255) COMMENT '',
`scopes` varchar(255) COMMENT '',
`read` boolean COMMENT 'Read scope',
`write` boolean COMMENT 'Write scope',
`follow` boolean COMMENT 'Follow scope',
PRIMARY KEY(`id`),
UNIQUE INDEX `client_id` (`client_id`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='OAuth application';
--
-- TABLE application-token
--
CREATE TABLE IF NOT EXISTS `application-token` (
`application-id` int unsigned NOT NULL COMMENT '',
`uid` mediumint unsigned NOT NULL COMMENT 'Owner User id',
`code` varchar(64) NOT NULL COMMENT '',
`access_token` varchar(64) NOT NULL COMMENT '',
`created_at` datetime NOT NULL COMMENT 'creation time',
`scopes` varchar(255) COMMENT '',
`read` boolean COMMENT 'Read scope',
`write` boolean COMMENT 'Write scope',
`follow` boolean COMMENT 'Follow scope',
PRIMARY KEY(`application-id`,`uid`),
INDEX `uid_id` (`uid`,`application-id`),
FOREIGN KEY (`application-id`) REFERENCES `application` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='OAuth user token';
--
-- TABLE attach
--
@ -1470,6 +1507,28 @@ CREATE TABLE IF NOT EXISTS `workerqueue` (
INDEX `done_pid_priority_created` (`done`,`pid`,`priority`,`created`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Background tasks queue entries';
--
-- VIEW application-view
--
DROP VIEW IF EXISTS `application-view`;
CREATE VIEW `application-view` AS SELECT
`application`.`id` AS `id`,
`application-token`.`uid` AS `uid`,
`application`.`name` AS `name`,
`application`.`redirect_uri` AS `redirect_uri`,
`application`.`website` AS `website`,
`application`.`client_id` AS `client_id`,
`application`.`client_secret` AS `client_secret`,
`application-token`.`code` AS `code`,
`application-token`.`access_token` AS `access_token`,
`application-token`.`created_at` AS `created_at`,
`application-token`.`scopes` AS `scopes`,
`application-token`.`read` AS `read`,
`application-token`.`write` AS `write`,
`application-token`.`follow` AS `follow`
FROM `application-token`
INNER JOIN `application` ON `application-token`.`application-id` = `application`.`id`;
--
-- VIEW post-user-view
--

View File

@ -9,19 +9,43 @@ Friendica provides the following endpoints defined in [the official Mastodon API
Authentication is the same as described in [Using the APIs](help/api#Authentication).
## Clients
Supported mobile apps:
- Tusky
- Husky
- twitlatte
- AndStatus
- Twidere
- Subway Tooter
Unsupported mobile apps:
- [Mammut](https://github.com/jamiesanson/Mammut) There are problems with the token request, see issue https://github.com/jamiesanson/Mammut/issues/19
- [Fedilab](https://framagit.org/tom79/fedilab) Automatically uses the legacy API, see issue: https://framagit.org/tom79/fedilab/-/issues/520
## Entities
These endpoints use the [Mastodon API entities](https://docs.joinmastodon.org/entities/).
## Implemented endpoints
- [`GET /api/v1//accounts/:id`](https://docs.joinmastodon.org/methods/accounts/#retrieve-information)
- [`GET /api/v1//accounts/:id/statuses`](https://docs.joinmastodon.org/methods/accounts/#retrieve-information)
- [`GET /api/v1/accounts/:id`](https://docs.joinmastodon.org/methods/accounts/#retrieve-information)
- [`GET /api/v1/accounts/:id/statuses`](https://docs.joinmastodon.org/methods/accounts/#retrieve-information)
- [`GET /api/v1/accounts/:id/followers`](https://docs.joinmastodon.org/methods/accounts/)
- [`GET /api/v1/accounts/:id/following`](https://docs.joinmastodon.org/methods/accounts/)
- [`GET /api/v1/accounts/:id/lists`](https://docs.joinmastodon.org/methods/accounts/)
- [`GET /api/v1/accounts/search`](https://docs.joinmastodon.org/methods/accounts)
- [`GET /api/v1/accounts/verify_credentials`](https://docs.joinmastodon.org/methods/accounts)
- [`GET /api/v1/blocks`](https://docs.joinmastodon.org/methods/accounts/blocks/)
- [`GET /api/v1/bookmarks`](https://docs.joinmastodon.org/methods/accounts/bookmarks/)
- [`GET /api/v1/custom_emojis`](https://docs.joinmastodon.org/methods/instance/custom_emojis/)
- Doesn't return unicode emojis since they aren't using an image URL
- [`GET /api/v1/directory`](https://docs.joinmastodon.org/methods/instance/directory/)
- [`GET /api/v1/favourites`](https://docs.joinmastodon.org/methods/accounts/favourites/)
- [`GET /api/v1/follow_requests`](https://docs.joinmastodon.org/methods/accounts/follow_requests#pending-follows)
- Returned IDs are specific to follow requests
- [`POST /api/v1/follow_requests/:id/authorize`](https://docs.joinmastodon.org/methods/accounts/follow_requests#accept-follow)
@ -36,7 +60,23 @@ 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/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/:id`](https://docs.joinmastodon.org/methods/timelines/lists/)
- [`GET /api/v1/lists/:id/accounts`](https://docs.joinmastodon.org/methods/timelines/lists/)
- [`GET /api/v1/media/:id`](https://docs.joinmastodon.org/methods/statuses/media/)
- [`GET /api/v1/mutes`](https://docs.joinmastodon.org/methods/accounts/mutes/)
- [`GET /api/v1/notifications`](https://docs.joinmastodon.org/methods/notifications/)
- [`GET /api/v1/notifications/:id`](https://docs.joinmastodon.org/methods/notifications/)
- [`GET /api/v1/preferences`](https://docs.joinmastodon.org/methods/accounts/preferences/)
- [`GET /api/v1/statuses/:id`](https://docs.joinmastodon.org/methods/statuses/)
- [`GET /api/v1/statuses/:id/context`](https://docs.joinmastodon.org/methods/statuses/)
- [`GET /api/v1/statuses/:id/reblogged_by`](https://docs.joinmastodon.org/methods/statuses/)
- [`GET /api/v1/statuses/:id/favourited_by`](https://docs.joinmastodon.org/methods/statuses/)
- [`GET /api/v1/suggestions`](https://docs.joinmastodon.org/methods/accounts/suggestions/)
- [`GET /api/v1/timelines/home`](https://docs.joinmastodon.org/methods/timelines/)
- [`GET /api/v1/timelines/list/:id`](https://docs.joinmastodon.org/methods/timelines/)
- [`GET /api/v1/timelines/public`](https://docs.joinmastodon.org/methods/timelines/)
- [`GET /api/v1/timelines/tag/:hashtag`](https://docs.joinmastodon.org/methods/timelines/)
- [`GET /api/v1/trends`](https://docs.joinmastodon.org/methods/instance/trends/)
## Non-implemented endpoints

View File

@ -175,6 +175,7 @@ function api_register_func($path, $func, $auth = false, $method = API_METHOD_ANY
* Simple Auth allow username in form of <pre>user@server</pre>, ignoring server part
*
* @param App $a App
* @param bool $do_login try to log in when not logged in, otherwise quit silently
* @throws ForbiddenException
* @throws InternalServerErrorException
* @throws UnauthorizedException
@ -185,8 +186,10 @@ function api_register_func($path, $func, $auth = false, $method = API_METHOD_ANY
* 'authenticated' => return status,
* 'user_record' => return authenticated user record
*/
function api_login(App $a)
function api_login(App $a, bool $do_login = true)
{
$_SESSION["allow_api"] = false;
// workaround for HTTP-auth in CGI mode
if (!empty($_SERVER['REDIRECT_REMOTE_USER'])) {
$userpass = base64_decode(substr($_SERVER["REDIRECT_REMOTE_USER"], 6));
@ -216,6 +219,10 @@ function api_login(App $a)
Logger::warning(API_LOG_PREFIX . 'OAuth error', ['module' => 'api', 'action' => 'login', 'exception' => $e->getMessage()]);
}
if (!$do_login) {
return;
}
Logger::debug(API_LOG_PREFIX . 'failed', ['module' => 'api', 'action' => 'login', 'parameters' => $_SERVER]);
header('WWW-Authenticate: Basic realm="Friendica"');
throw new UnauthorizedException("This API requires login");
@ -247,7 +254,7 @@ function api_login(App $a)
*/
Hook::callAll('authenticate', $addon_auth);
if ($addon_auth['authenticated'] && count($addon_auth['user_record'])) {
if ($addon_auth['authenticated'] && !empty($addon_auth['user_record'])) {
$record = $addon_auth['user_record'];
} else {
$user_id = User::authenticate(trim($user), trim($password), true);
@ -257,6 +264,9 @@ function api_login(App $a)
}
if (!DBA::isResult($record)) {
if (!$do_login) {
return;
}
Logger::debug(API_LOG_PREFIX . 'failed', ['module' => 'api', 'action' => 'login', 'parameters' => $_SERVER]);
header('WWW-Authenticate: Basic realm="Friendica"');
//header('HTTP/1.0 401 Unauthorized');
@ -1021,7 +1031,7 @@ function api_statuses_mediap($type)
$_REQUEST['profile_uid'] = api_user();
$_REQUEST['api_source'] = true;
$txt = requestdata('status');
$txt = requestdata('status') ?? '';
/// @TODO old-lost code?
//$txt = urldecode(requestdata('status'));
@ -1076,7 +1086,7 @@ function api_statuses_update($type)
// convert $_POST array items to the form we use for web posts.
if (requestdata('htmlstatus')) {
$txt = requestdata('htmlstatus');
$txt = requestdata('htmlstatus') ?? '';
if ((strpos($txt, '<') !== false) || (strpos($txt, '>') !== false)) {
$txt = HTML::toBBCodeVideo($txt);
@ -1157,30 +1167,56 @@ function api_statuses_update($type)
}
}
if (!empty($_FILES['media'])) {
if (requestdata('media_ids')) {
$ids = explode(',', requestdata('media_ids') ?? '');
} elseif (!empty($_FILES['media'])) {
// upload the image if we have one
$picture = wall_upload_post($a, false);
if (is_array($picture)) {
$_REQUEST['body'] .= "\n\n" . '[url=' . $picture["albumpage"] . '][img]' . $picture["preview"] . "[/img][/url]";
$ids[] = $picture['id'];
}
}
if (requestdata('media_ids')) {
$ids = explode(',', requestdata('media_ids'));
$attachments = [];
$ressources = [];
if (!empty($ids)) {
foreach ($ids as $id) {
$r = q(
"SELECT `resource-id`, `scale`, `nickname`, `type`, `desc` FROM `photo` INNER JOIN `user` ON `user`.`uid` = `photo`.`uid` WHERE `resource-id` IN (SELECT `resource-id` FROM `photo` WHERE `id` = %d) AND `scale` > 0 AND `photo`.`uid` = %d ORDER BY `photo`.`width` DESC LIMIT 1",
intval($id),
api_user()
);
if (DBA::isResult($r)) {
$media = DBA::toArray(DBA::p("SELECT `resource-id`, `scale`, `nickname`, `type`, `desc`, `filename`, `datasize`, `width`, `height` FROM `photo`
INNER JOIN `user` ON `user`.`uid` = `photo`.`uid` WHERE `resource-id` IN
(SELECT `resource-id` FROM `photo` WHERE `id` = ?) AND `photo`.`uid` = ?
ORDER BY `photo`.`width` DESC LIMIT 2", $id, api_user()));
if (!empty($media)) {
$ressources[] = $media[0]['resource-id'];
$phototypes = Images::supportedTypes();
$ext = $phototypes[$r[0]['type']];
$description = $r[0]['desc'] ?? '';
$_REQUEST['body'] .= "\n\n" . '[url=' . DI::baseUrl() . '/photos/' . $r[0]['nickname'] . '/image/' . $r[0]['resource-id'] . ']';
$_REQUEST['body'] .= '[img=' . DI::baseUrl() . '/photo/' . $r[0]['resource-id'] . '-' . $r[0]['scale'] . '.' . $ext . ']' . $description . '[/img][/url]';
$ext = $phototypes[$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'],
'description' => $media[0]['desc'] ?? '',
'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;
$attachment['preview-width'] = $media[1]['width'];
$attachment['preview-height'] = $media[1]['height'];
}
$attachments[] = $attachment;
}
}
// We have to avoid that the post is rejected because of an empty body
if (empty($_REQUEST['body'])) {
$_REQUEST['body'] = '[hr]';
}
}
if (!empty($attachments)) {
$_REQUEST['attachments'] = $attachments;
}
// set this so that the item_post() function is quiet and doesn't redirect or emit json
@ -1194,6 +1230,13 @@ function api_statuses_update($type)
// call out normal post function
$item_id = item_post($a);
if (!empty($ressources) && !empty($item_id)) {
$item = Post::selectFirst(['uri-id', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid'], ['id' => $item_id]);
foreach ($ressources as $ressource) {
Photo::setPermissionForRessource($ressource, api_user(), $item['allow_cid'], $item['allow_gid'], $item['deny_cid'], $item['deny_gid']);
}
}
// output the post that we just posted.
return api_status_show($type, $item_id);
}
@ -2534,7 +2577,7 @@ function api_convert_item($item)
$statustext = mb_substr($statustext, 0, 1000) . "... \n" . ($item['plink'] ?? '');
}
$statushtml = BBCode::convert(BBCode::removeAttachment($body), false);
$statushtml = BBCode::convert(BBCode::removeAttachment($body), false, BBCode::API, true);
// Workaround for clients with limited HTML parser functionality
$search = ["<br>", "<blockquote>", "</blockquote>",
@ -2585,25 +2628,7 @@ function api_convert_item($item)
*/
function api_add_attachments_to_body(array $item)
{
$body = $item['body'];
foreach (Post\Media::getByURIId($item['uri-id'], [Post\Media::IMAGE, Post\Media::AUDIO, Post\Media::VIDEO]) as $media) {
if (Item::containsLink($item['body'], $media['url'])) {
continue;
}
if ($media['type'] == Post\Media::IMAGE) {
if (!empty($media['description'])) {
$body .= "\n[img=" . $media['url'] . ']' . $media['description'] .'[/img]';
} else {
$body .= "\n[img]" . $media['url'] .'[/img]';
}
} elseif ($media['type'] == Post\Media::AUDIO) {
$body .= "\n[audio]" . $media['url'] . "[/audio]\n";
} elseif ($media['type'] == Post\Media::VIDEO) {
$body .= "\n[video]" . $media['url'] . "[/video]\n";
}
}
$body = Post\Media::addAttachmentsToBody($item['uri-id'], $item['body']);
if (strpos($body, '[/img]') !== false) {
return $body;

View File

@ -888,7 +888,8 @@ function conversation_fetch_items(array $parent, array $items, array $condition,
return $items;
}
function item_photo_menu($item) {
function item_photo_menu($item)
{
$sub_link = '';
$poke_link = '';
$contact_url = '';
@ -929,8 +930,8 @@ function item_photo_menu($item) {
if (!empty($pcid)) {
$contact_url = 'contact/' . $pcid;
$posts_link = $contact_url . '/posts';
$block_link = $contact_url . '/block';
$ignore_link = $contact_url . '/ignore';
$block_link = $item['self'] ? '' : $contact_url . '/block';
$ignore_link = $item['self'] ? '' : $contact_url . '/ignore';
}
if ($cid && !$item['self']) {
@ -983,7 +984,7 @@ function item_photo_menu($item) {
if (strpos($v, 'javascript:') === 0) {
$v = substr($v, 11);
$o .= '<li role="menuitem"><a onclick="' . $v . '">' . $k . '</a></li>' . PHP_EOL;
} elseif ($v!='') {
} elseif ($v) {
$o .= '<li role="menuitem"><a href="' . $v . '">' . $k . '</a></li>' . PHP_EOL;
}
}
@ -1211,6 +1212,7 @@ function status_editor(App $a, $x, $notes_cid = 0, $popup = false)
'$edimg' => DI::l10n()->t('Image'),
'$edurl' => DI::l10n()->t('Link'),
'$edattach' => DI::l10n()->t('Link or Media'),
'$edvideo' => DI::l10n()->t('Video'),
'$setloc' => DI::l10n()->t('Set your location'),
'$shortsetloc' => DI::l10n()->t('set location'),
'$noloc' => DI::l10n()->t('Clear browser location'),

View File

@ -41,7 +41,7 @@ use Friendica\Protocol\Activity;
* type, event, otype, activity, verb, uid, cid, origin_cid, item, link,
* source_name, source_mail, source_nick, source_link, source_photo,
* show_in_notification_page
*
*
* @return bool
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
@ -273,7 +273,7 @@ function notification($params)
$epreamble = $l10n->t('%1$s [url=%2$s]shared a post[/url] from %3$s.',
'[url='.$params['source_link'].']'.$params['source_name'].'[/url]',
$params['link'], '[url='.$params['origin_link'].']'.$params['origin_name'].'[/url]'
);
);
}
$sitelink = $l10n->t('Please visit %s to view and/or reply to the conversation.');

View File

@ -181,7 +181,7 @@ function dfrn_confirm_post(App $a, $handsfree = null)
* random key which is encrypted with their site public key.
*/
$src_aes_key = openssl_random_pseudo_bytes(64);
$src_aes_key = random_bytes(64);
$result = '';
openssl_private_encrypt($dfrn_id, $result, $user['prvkey']);

View File

@ -614,7 +614,8 @@ function item_post(App $a) {
$datarray['origin'] = $origin;
$datarray['object'] = $object;
$datarray["uri-id"] = ItemURI::getIdByURI($datarray["uri"]);
$datarray['uri-id'] = ItemURI::getIdByURI($datarray['uri']);
$datarray['attachments'] = $_REQUEST['attachments'] ?? [];
/*
* These fields are for the convenience of addons...

View File

@ -139,7 +139,7 @@ function ping_init(App $a)
local_user(), Verb::getID(Activity::FOLLOW)];
$items = Post::selectForUser(local_user(), ['wall', 'uid', 'uri-id'], $condition);
if (DBA::isResult($items)) {
$items_unseen = Post::toArray($items);
$items_unseen = Post::toArray($items, false);
$arr = ['items' => $items_unseen];
Hook::callAll('network_ping', $arr);

View File

@ -500,77 +500,26 @@ function settings_content(App $a)
}
if (($a->argc > 1) && ($a->argv[1] === 'oauth')) {
if (($a->argc > 2) && ($a->argv[2] === 'add')) {
$tpl = Renderer::getMarkupTemplate('settings/oauth_edit.tpl');
$o .= Renderer::replaceMacros($tpl, [
'$form_security_token' => BaseModule::getFormSecurityToken("settings_oauth"),
'$title' => DI::l10n()->t('Add application'),
'$submit' => DI::l10n()->t('Save Settings'),
'$cancel' => DI::l10n()->t('Cancel'),
'$name' => ['name', DI::l10n()->t('Name'), '', ''],
'$key' => ['key', DI::l10n()->t('Consumer Key'), '', ''],
'$secret' => ['secret', DI::l10n()->t('Consumer Secret'), '', ''],
'$redirect' => ['redirect', DI::l10n()->t('Redirect'), '', ''],
'$icon' => ['icon', DI::l10n()->t('Icon url'), '', ''],
]);
return $o;
}
if (($a->argc > 3) && ($a->argv[2] === 'edit')) {
$r = q("SELECT * FROM clients WHERE client_id='%s' AND uid=%d",
DBA::escape($a->argv[3]),
local_user());
if (!DBA::isResult($r)) {
notice(DI::l10n()->t("You can't edit this application."));
return;
}
$app = $r[0];
$tpl = Renderer::getMarkupTemplate('settings/oauth_edit.tpl');
$o .= Renderer::replaceMacros($tpl, [
'$form_security_token' => BaseModule::getFormSecurityToken("settings_oauth"),
'$title' => DI::l10n()->t('Add application'),
'$submit' => DI::l10n()->t('Update'),
'$cancel' => DI::l10n()->t('Cancel'),
'$name' => ['name', DI::l10n()->t('Name'), $app['name'] , ''],
'$key' => ['key', DI::l10n()->t('Consumer Key'), $app['client_id'], ''],
'$secret' => ['secret', DI::l10n()->t('Consumer Secret'), $app['pw'], ''],
'$redirect' => ['redirect', DI::l10n()->t('Redirect'), $app['redirect_uri'], ''],
'$icon' => ['icon', DI::l10n()->t('Icon url'), $app['icon'], ''],
]);
return $o;
}
if (($a->argc > 3) && ($a->argv[2] === 'delete')) {
BaseModule::checkFormSecurityTokenRedirectOnError('/settings/oauth', 'settings_oauth', 't');
DBA::delete('clients', ['client_id' => $a->argv[3], 'uid' => local_user()]);
DBA::delete('application-token', ['application-id' => $a->argv[3], 'uid' => local_user()]);
DI::baseUrl()->redirect('settings/oauth/', true);
return;
}
/// @TODO validate result with DBA::isResult()
$r = q("SELECT clients.*, tokens.id as oauth_token, (clients.uid=%d) AS my
FROM clients
LEFT JOIN tokens ON clients.client_id=tokens.client_id
WHERE clients.uid IN (%d, 0)",
local_user(),
local_user());
$applications = DBA::selectToArray('application-view', ['id', 'uid', 'name', 'website', 'scopes', 'created_at'], ['uid' => local_user()]);
$tpl = Renderer::getMarkupTemplate('settings/oauth.tpl');
$o .= Renderer::replaceMacros($tpl, [
'$form_security_token' => BaseModule::getFormSecurityToken("settings_oauth"),
'$baseurl' => DI::baseUrl()->get(true),
'$title' => DI::l10n()->t('Connected Apps'),
'$add' => DI::l10n()->t('Add application'),
'$edit' => DI::l10n()->t('Edit'),
'$delete' => DI::l10n()->t('Delete'),
'$consumerkey' => DI::l10n()->t('Client key starts with'),
'$noname' => DI::l10n()->t('No name'),
'$remove' => DI::l10n()->t('Remove authorization'),
'$apps' => $r,
'$baseurl' => DI::baseUrl()->get(true),
'$title' => DI::l10n()->t('Connected Apps'),
'$name' => DI::l10n()->t('Name'),
'$website' => DI::l10n()->t('Home Page'),
'$created_at' => DI::l10n()->t('Created'),
'$delete' => DI::l10n()->t('Remove authorization'),
'$apps' => $applications,
]);
return $o;
}

View File

@ -1,495 +0,0 @@
# Drone.io test YML-File, currently disabled
# See https://github.com/friendica/friendica/pull/7643 for further infos
kind: pipeline
name: mysql8.0-php7.1
steps:
- name: mysql8.0-php7.1
image: friendicaci/php7.1:php7.1.32
commands:
- NOCOVERAGE=true ./autotest.sh mysql
environment:
MYSQL_USERNAME: friendica
MYSQL_PASSWORD: friendica
MYSQL_DATABASE: friendica
MYSQL_HOST: mysql
node:
test: db
services:
- name: mysql
image: mysql:8.0
command: [ "--default-authentication-plugin=mysql_native_password" ]
environment:
MYSQL_ROOT_PASSWORD: friendica
MYSQL_USER: friendica
MYSQL_PASSWORD: friendica
MYSQL_DATABASE: friendica
volumes:
- name: cache
path: /var/lib/mysql
volumes:
- name: cache
temp: {}
trigger:
branch:
# - stable
- develop
# - "*-rc"
# event:
# - pull_request
# - push
---
kind: pipeline
name: mysql8.0-php7.2
steps:
- name: mysql8.0-php7.2
image: friendicaci/php7.2:php7.2.22
commands:
- NOCOVERAGE=true ./autotest.sh mysql
environment:
MYSQL_USERNAME: friendica
MYSQL_PASSWORD: friendica
MYSQL_DATABASE: friendica
MYSQL_HOST: mysql
node:
test: db
services:
- name: mysql
image: mysql:8.0
command: [ "--default-authentication-plugin=mysql_native_password" ]
environment:
MYSQL_ROOT_PASSWORD: friendica
MYSQL_USER: friendica
MYSQL_PASSWORD: friendica
MYSQL_DATABASE: friendica
volumes:
- name: cache
path: /var/lib/mysql
volumes:
- name: cache
temp: {}
trigger:
branch:
# - stable
- develop
# - "*-rc"
# event:
# - pull_request
# - push
---
kind: pipeline
name: mysql8.0-php7.3
steps:
- name: mysql8.0-php7.3
image: friendicaci/php7.3:php7.3.9
commands:
- NOCOVERAGE=true ./autotest.sh mysql
environment:
MYSQL_USERNAME: friendica
MYSQL_PASSWORD: friendica
MYSQL_DATABASE: friendica
MYSQL_HOST: mysql
node:
test: db
services:
- name: mysql
image: mysql:8.0
command: [ "--default-authentication-plugin=mysql_native_password" ]
environment:
MYSQL_ROOT_PASSWORD: friendica
MYSQL_USER: friendica
MYSQL_PASSWORD: friendica
MYSQL_DATABASE: friendica
volumes:
- name: cache
path: /var/lib/mysql
volumes:
- name: cache
temp: {}
trigger:
branch:
# - stable
- develop
# - "*-rc"
# event:
# - pull_request
# - push
---
kind: pipeline
name: mariadb10.1-php7.1
steps:
- name: mariadb10.1-php7.1
image: friendicaci/php7.1:php7.1.32
commands:
- phpenmod xdebug
- sleep 20
- ./autotest.sh mariadb
- wget https://codecov.io/bash -O codecov.sh
- sh -c "if [ '$DRONE_BUILD_EVENT' = 'pull_request' ]; then bash codecov.sh -B $DRONE_BRANCH -C $DRONE_COMMIT -P $DRONE_PULL_REQUEST -t 5ce7d64e-07b4-4adf-8700-e2eae27e14ec -f tests/autotest-clover.xml; fi"
- sh -c "if [ '$DRONE_BUILD_EVENT' != 'pull_request' ]; then bash codecov.sh -B $DRONE_BRANCH -C $DRONE_COMMIT -t 5ce7d64e-07b4-4adf-8700-e2eae27e14ec -f tests/autotest-clover.xml; fi"
environment:
MYSQL_USER: friendica
MYSQL_PASSWORD: friendica
MYSQL_DATABASE: friendica
MYSQL_HOST: mariadb
node:
test: db
services:
- name: mariadb
image: mariadb:10.1
environment:
MYSQL_ROOT_PASSWORD: friendica
MYSQL_USER: friendica
MYSQL_PASSWORD: friendica
MYSQL_DATABASE: friendica
volumes:
- name: cache
path: /var/lib/mysql
volumes:
- name: cache
temp: {}
trigger:
branch:
# - stable
- develop
# - "*-rc"
# event:
# - pull_request
# - push
---
kind: pipeline
name: mariadb10.1-php7.2
steps:
- name: mariadb10.1-php7.2
image: friendicaci/php7.2:php7.2.22
commands:
- NOCOVERAGE=true ./autotest.sh mariadb
environment:
MYSQL_USER: friendica
MYSQL_PASSWORD: friendica
MYSQL_DATABASE: friendica
MYSQL_HOST: mariadb
node:
test: db
services:
- name: mariadb
image: mariadb:10.1
environment:
MYSQL_ROOT_PASSWORD: friendica
MYSQL_USER: friendica
MYSQL_PASSWORD: friendica
MYSQL_DATABASE: friendica
volumes:
- name: cache
path: /var/lib/mysql
volumes:
- name: cache
temp: {}
trigger:
branch:
# - stable
- develop
# - "*-rc"
# event:
# - pull_request
# - push
---
kind: pipeline
name: mariadb10.1-php7.3
steps:
- name: mariadb10.1-php7.3
image: friendicaci/php7.3:php7.3.9
commands:
- NOCOVERAGE=true ./autotest.sh mariadb
environment:
MYSQL_USER: friendica
MYSQL_PASSWORD: friendica
MYSQL_DATABASE: friendica
MYSQL_HOST: mariadb
node:
test: db
services:
- name: mariadb
image: mariadb:10.1
environment:
MYSQL_ROOT_PASSWORD: friendica
MYSQL_USER: friendica
MYSQL_PASSWORD: friendica
MYSQL_DATABASE: friendica
volumes:
- name: cache
path: /var/lib/mysql
volumes:
- name: cache
temp: {}
trigger:
branch:
# - stable
- develop
# - "*-rc"
# event:
# - pull_request
# - push
---
kind: pipeline
name: redis-php7.1
steps:
- name: redis-php7.1
image: friendicaci/php7.1:php7.1.32
commands:
- phpenmod xdebug
- sleep 20
- NOINSTALL=true TEST_SELECTION=REDIS ./autotest.sh mysql
- wget https://codecov.io/bash -O codecov.sh
- sh -c "if [ '$DRONE_BUILD_EVENT' = 'pull_request' ]; then bash codecov.sh -B $DRONE_BRANCH -C $DRONE_COMMIT -P $DRONE_PULL_REQUEST -t 5ce7d64e-07b4-4adf-8700-e2eae27e14ec -f tests/autotest-clover.xml; fi"
- sh -c "if [ '$DRONE_BUILD_EVENT' != 'pull_request' ]; then bash codecov.sh -B $DRONE_BRANCH -C $DRONE_COMMIT -t 5ce7d64e-07b4-4adf-8700-e2eae27e14ec -f tests/autotest-clover.xml; fi"
environment:
REDIS_HOST: redis
services:
- name: redis
image: redis
trigger:
branch:
# - stable
- develop
# - "*-rc"
# event:
# - pull_request
# - push
---
kind: pipeline
name: redis-php7.2
steps:
- name: redis-php7.2
image: friendicaci/php7.2:php7.2.22
commands:
- NOCOVERAGE=true NOINSTALL=true TEST_SELECTION=REDIS ./autotest.sh mysql
environment:
REDIS_HOST: redis
services:
- name: redis
image: redis
trigger:
branch:
# - stable
- develop
# - "*-rc"
# event:
# - pull_request
# - push
---
kind: pipeline
name: redis-php7.3
steps:
- name: redis-php7.3
image: friendicaci/php7.3:php7.3.9
commands:
- NOCOVERAGE=true NOINSTALL=true TEST_SELECTION=REDIS ./autotest.sh mysql
environment:
REDIS_HOST: redis
services:
- name: redis
image: redis
trigger:
branch:
# - stable
- develop
# - "*-rc"
# event:
# - pull_request
# - push
---
kind: pipeline
name: memcache-php7.1
steps:
- name: memcache-php7.1
image: friendicaci/php7.1:php7.1.32
commands:
- phpenmod xdebug
- sleep 20
- NOINSTALL=true TEST_SELECTION=MEMCACHE ./autotest.sh mysql
- wget https://codecov.io/bash -O codecov.sh
- sh -c "if [ '$DRONE_BUILD_EVENT' = 'pull_request' ]; then bash codecov.sh -B $DRONE_BRANCH -C $DRONE_COMMIT -P $DRONE_PULL_REQUEST -t 5ce7d64e-07b4-4adf-8700-e2eae27e14ec -f tests/autotest-clover.xml; fi"
- sh -c "if [ '$DRONE_BUILD_EVENT' != 'pull_request' ]; then bash codecov.sh -B $DRONE_BRANCH -C $DRONE_COMMIT -t 5ce7d64e-07b4-4adf-8700-e2eae27e14ec -f tests/autotest-clover.xml; fi"
environment:
MEMCACHE_HOST: memcached
services:
- name: memcached
image: memcached
trigger:
branch:
# - stable
- develop
# - "*-rc"
# event:
# - pull_request
# - push
---
kind: pipeline
name: memcache-php7.2
steps:
- name: memcache-php7.2
image: friendicaci/php7.2:php7.2.22
commands:
- NOCOVERAGE=true NOINSTALL=true TEST_SELECTION=MEMCACHE ./autotest.sh mysql
environment:
MEMCACHE_HOST: memcached
services:
- name: memcached
image: memcached
trigger:
branch:
# - stable
- develop
# - "*-rc"
# event:
# - pull_request
# - push
---
kind: pipeline
name: memcache-php7.3
steps:
- name: memcache-php7.3
image: friendicaci/php7.3:php7.3.9
commands:
- NOCOVERAGE=true NOINSTALL=true TEST_SELECTION=MEMCACHE ./autotest.sh mysql
environment:
MEMCACHE_HOST: memcached
services:
- name: memcached
image: memcached
trigger:
branch:
# - stable
- develop
# - "*-rc"
# event:
# - pull_request
# - push
---
kind: pipeline
name: memcached-php7.1
steps:
- name: memcached-php7.1
image: friendicaci/php7.1:php7.1.32
commands:
- phpenmod xdebug
- sleep 20
- NOINSTALL=true TEST_SELECTION=MEMCACHED ./autotest.sh mysql
- wget https://codecov.io/bash -O codecov.sh
- sh -c "if [ '$DRONE_BUILD_EVENT' = 'pull_request' ]; then bash codecov.sh -B $DRONE_BRANCH -C $DRONE_COMMIT -P $DRONE_PULL_REQUEST -t 5ce7d64e-07b4-4adf-8700-e2eae27e14ec -f tests/autotest-clover.xml; fi"
- sh -c "if [ '$DRONE_BUILD_EVENT' != 'pull_request' ]; then bash codecov.sh -B $DRONE_BRANCH -C $DRONE_COMMIT -t 5ce7d64e-07b4-4adf-8700-e2eae27e14ec -f tests/autotest-clover.xml; fi"
environment:
MEMCACHED_HOST: memcached
services:
- name: memcached
image: memcached
trigger:
branch:
# - stable
- develop
# - "*-rc"
# event:
# - pull_request
# - push
---
kind: pipeline
name: memcached-php7.2
steps:
- name: memcached-php7.2
image: friendicaci/php7.2:php7.2.22
commands:
- NOCOVERAGE=true NOINSTALL=true TEST_SELECTION=MEMCACHED ./autotest.sh mysql
environment:
MEMCACHED_HOST: memcached
services:
- name: memcached
image: memcached
trigger:
branch:
# - stable
- develop
# - "*-rc"
# event:
# - pull_request
# - push
---
kind: pipeline
name: memcached-php7.3
steps:
- name: memcached-php7.3
image: friendicaci/php7.3:php7.3.9
commands:
- NOCOVERAGE=true NOINSTALL=true TEST_SELECTION=MEMCACHED ./autotest.sh mysql
environment:
MEMCACHED_HOST: memcached
services:
- name: memcached
image: memcached
trigger:
branch:
# - stable
- develop
# - "*-rc"
# event:
# - pull_request
# - push

29
mods/release-list.txt Normal file
View File

@ -0,0 +1,29 @@
bin/auth_ejabberd.php
bin/console
bin/console.bat
bin/console.php
bin/daemon.php
bin/testargs.php
bin/worker.php
config/
doc/
images/
include/
mod/
mods/
spec/
src/
static/
vendor/
view/
.htaccess-dist
boot.php
CHANGELOG
CREDITS.txt
database.sql
index.php
LICENSE
README.md
security.txt
update.php
VERSION

View File

@ -276,11 +276,23 @@ class Module
$profiler->set(microtime(true) - $timestamp, 'init');
if ($server['REQUEST_METHOD'] === 'POST') {
if ($server['REQUEST_METHOD'] === Router::DELETE) {
call_user_func([$this->module_class, 'delete'], $this->module_parameters);
}
if ($server['REQUEST_METHOD'] === Router::PATCH) {
call_user_func([$this->module_class, 'patch'], $this->module_parameters);
}
if ($server['REQUEST_METHOD'] === Router::POST) {
Core\Hook::callAll($this->module . '_mod_post', $post);
call_user_func([$this->module_class, 'post'], $this->module_parameters);
}
if ($server['REQUEST_METHOD'] === Router::PUT) {
call_user_func([$this->module_class, 'put'], $this->module_parameters);
}
Core\Hook::callAll($this->module . '_mod_afterpost', $placeholder);
call_user_func([$this->module_class, 'afterpost'], $this->module_parameters);

View File

@ -72,6 +72,26 @@ abstract class BaseModule
return $o;
}
/**
* Module DELETE method to process submitted data
*
* Extend this method if the module is supposed to process DELETE requests.
* Doesn't display any content
*/
public static function delete(array $parameters = [])
{
}
/**
* Module PATCH method to process submitted data
*
* Extend this method if the module is supposed to process PATCH requests.
* Doesn't display any content
*/
public static function patch(array $parameters = [])
{
}
/**
* Module POST method to process submitted data
*
@ -92,6 +112,16 @@ abstract class BaseModule
{
}
/**
* Module PUT method to process submitted data
*
* Extend this method if the module is supposed to process PUT requests.
* Doesn't display any content
*/
public static function put(array $parameters = [])
{
}
/*
* Functions used to protect against Cross-Site Request Forgery
* The security token has to base on at least one value that an attacker can't know - here it's the session ID and the private key.

View File

@ -128,6 +128,10 @@ HELP;
throw new RuntimeException("$cat.$key is an array and can't be set using this command.");
}
if ($this->config->get($cat, $key) === $value) {
throw new RuntimeException("$cat.$key already set to $value.");
}
$result = $this->config->set($cat, $key, $value);
if ($result) {
$this->out("{$cat}.{$key} <= " .

287
src/Console/Contact.php Normal file
View File

@ -0,0 +1,287 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Console;
use Console_Table;
use Friendica\App;
use Friendica\Model\Contact as ContactModel;
use Friendica\Model\User as UserModel;
use Friendica\Util\Temporal;
use RuntimeException;
use Seld\CliPrompt\CliPrompt;
/**
* tool to manage contacts of users of the current node
*/
class Contact extends \Asika\SimpleConsole\Console
{
protected $helpOptions = ['h', 'help', '?'];
/**
* @var App\Mode
*/
private $appMode;
/**
* @var IPConfig
*/
private $pConfig;
protected function getHelp()
{
$help = <<<HELP
console contact - Modify contact settings per console commands.
Usage
bin/console contact add <user nick> <URL> [<network>] [-h|--help|-?] [-v]
bin/console contact remove <CID> [-h|--help|-?] [-v]
bin/console contact search id <CID> [-h|--help|-?] [-v]
bin/console contact search url <user nick> <URL> [-h|--help|-?] [-v]
bin/console contact terminate <CID> [-h|--help|-?] [-v]
Description
Modify contact settings per console commands.
Options
-h|--help|-? Show help information
-v Show more debug information
-y Non-interactive mode, assume "yes" as answer to the user deletion prompt
HELP;
return $help;
}
public function __construct(App\Mode $appMode, array $argv = null)
{
parent::__construct($argv);
$this->appMode = $appMode;
}
protected function doExecute()
{
if ($this->getOption('v')) {
$this->out('Class: ' . __CLASS__);
$this->out('Arguments: ' . var_export($this->args, true));
$this->out('Options: ' . var_export($this->options, true));
}
if (count($this->args) == 0) {
$this->out($this->getHelp());
return 0;
}
if ($this->appMode->isInstall()) {
throw new RuntimeException('Database isn\'t ready or populated yet');
}
$command = $this->getArgument(0);
switch ($command) {
case 'add':
return $this->addContact();
case 'remove':
return $this->removeContact();
case 'search':
return $this->searchContact();
case 'terminate':
return $this->terminateContact();
default:
throw new \Asika\SimpleConsole\CommandArgsException('Wrong command.');
}
}
/**
* Retrieves the user from a nick supplied as an argument or from a prompt
*
* @param int $arg_index Index of the nick argument in the arguments list
*
* @return array|boolean User record with uid field, or false if user is not found
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private function getUserByNick($arg_index)
{
$nick = $this->getArgument($arg_index);
if (empty($nick)) {
$this->out('Enter user nickname: ');
$nick = CliPrompt::prompt();
if (empty($nick)) {
throw new RuntimeException('A user nickname must be specified.');
}
}
$user = UserModel::getByNickname($nick, ['uid', 'nickname']);
if (empty($user)) {
throw new RuntimeException('User not found');
}
return $user;
}
/**
* Adds a contact to a user from a URL
*
* @return bool True, if the command was successful
*/
private function addContact()
{
$user = $this->getUserByNick(1);
$url = $this->getArgument(2);
if (empty($url)) {
$this->out('Enter contact URL: ');
$url = CliPrompt::prompt();
if (empty($url)) {
throw new RuntimeException('A contact URL must be specified.');
}
}
$contact = ContactModel::getByURLForUser($url, $user['uid']);
if (!empty($contact)) {
throw new RuntimeException('Contact already exists');
}
$network = $this->getArgument(3);
if ($network === null) {
$this->out('Enter network, or leave blank: ');
$network = CliPrompt::prompt();
}
$result = ContactModel::createFromProbe($user, $url, false, $network);
if ($result['success']) {
$this->out('User ' . $user['nickname'] . ' now connected to ' . $url . ', contact ID ' . $result['cid']);
} else {
throw new RuntimeException($result['message']);
}
}
/**
* Sends an unfriend message. Does not remove the contact
*
* @return bool True, if the command was successful
*/
private function terminateContact()
{
$cid = $this->getArgument(1);
if (empty($cid)) {
$this->out('Enter contact ID: ');
$cid = CliPrompt::prompt();
if (empty($cid)) {
throw new RuntimeException('A contact ID must be specified.');
}
}
$contact = ContactModel::getById($cid);
if (empty($contact)) {
throw new RuntimeException('Contact not found');
}
$user = UserModel::getById($contact['uid']);
$result = ContactModel::terminateFriendship($user, $contact);
}
/**
* Marks a contact for removal
*
* @return bool True, if the command was successful
*/
private function removeContact()
{
$cid = $this->getArgument(1);
if (empty($cid)) {
$this->out('Enter contact ID: ');
$cid = CliPrompt::prompt();
if (empty($cid)) {
throw new RuntimeException('A contact ID must be specified.');
}
}
$result = ContactModel::remove($cid);
}
/**
* Returns a contact based on search parameter
*
* @return bool True, if the command was successful
*/
private function searchContact()
{
$fields = [
'id',
'uid',
'network',
'name',
'nick',
'url',
'addr',
'created',
'updated',
'blocked',
'deleted',
];
$subCmd = $this->getArgument(1);
$table = new Console_Table();
$table->setHeaders(['ID', 'UID', 'Network', 'Name', 'Nick', 'URL', 'E-Mail', 'Created', 'Updated', 'Blocked', 'Deleted']);
$addRow = function ($row) use (&$table) {
$table->addRow([
$row['id'],
$row['uid'],
$row['network'],
$row['name'],
$row['nick'],
$row['url'],
$row['addr'],
Temporal::getRelativeDate($row['created']),
Temporal::getRelativeDate($row['updated']),
$row['blocked'],
$row['deleted'],
]);
};
switch ($subCmd) {
case 'id':
$cid = $this->getArgument(2);
$contact = ContactModel::getById($cid, $fields);
if (!empty($contact)) {
$addRow($contact);
}
break;
case 'url':
$user = $this->getUserByNick(2);
$url = $this->getArgument(3);
$contact = ContactModel::getByURLForUser($url, $user['uid'], false, $fields);
if (!empty($contact)) {
$addRow($contact);
}
break;
default:
$this->out($this->getHelp());
return false;
}
$this->out($table->getTable());
return true;
}
}

View File

@ -26,7 +26,6 @@ use Friendica\App;
use Friendica\Content\Pager;
use Friendica\Core\L10n;
use Friendica\Core\PConfig\IPConfig;
use Friendica\Database\Database;
use Friendica\Model\Register;
use Friendica\Model\User as UserModel;
use Friendica\Util\Temporal;
@ -49,9 +48,9 @@ class User extends \Asika\SimpleConsole\Console
*/
private $l10n;
/**
* @var Database
* @var IPConfig
*/
private $dba;
private $pConfig;
protected function getHelp()
{
@ -89,13 +88,13 @@ HELP;
return $help;
}
public function __construct(App\Mode $appMode, L10n $l10n, Database $dba, array $argv = null)
public function __construct(App\Mode $appMode, L10n $l10n, IPConfig $pConfig, array $argv = null)
{
parent::__construct($argv);
$this->appMode = $appMode;
$this->l10n = $l10n;
$this->dba = $dba;
$this->appMode = $appMode;
$this->l10n = $l10n;
$this->pConfig = $pConfig;
}
protected function doExecute()
@ -171,15 +170,15 @@ HELP;
*
* @param int $arg_index Index of the nick argument in the arguments list
*
* @return mixed user data or dba failure result
* @return array|boolean User record with uid field, or false if user is not found
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private function getUserByNick($arg_index)
{
$nick = $this->getNick($arg_index);
$user = $this->dba->selectFirst('user', ['uid'], ['nickname' => $nick]);
if (!$this->dba->isResult($user)) {
$user = UserModel::getByNickname($nick, ['uid']);
if (empty($user)) {
throw new RuntimeException($this->l10n->t('User not found'));
}
@ -207,7 +206,7 @@ HELP;
try {
$result = UserModel::updatePassword($user['uid'], $password);
if (!$this->dba->isResult($result)) {
if (empty($result)) {
throw new \Exception($this->l10n->t('Password update failed. Please try again.'));
}
@ -338,8 +337,8 @@ HELP;
private function listUser()
{
$subCmd = $this->getArgument(1);
$start = $this->getOption(['s', 'start'], 0);
$count = $this->getOption(['c', 'count'], Pager::ITEMS_PER_PAGE);
$start = $this->getOption(['s', 'start'], 0);
$count = $this->getOption(['c', 'count'], Pager::ITEMS_PER_PAGE);
$table = new Console_Table();
@ -403,7 +402,7 @@ HELP;
];
$subCmd = $this->getArgument(1);
$param = $this->getArgument(2);
$param = $this->getArgument(2);
$table = new Console_Table();
$table->setHeaders(['UID', 'GUID', 'Name', 'Nick', 'E-Mail', 'Register', 'Login', 'Verified', 'Blocked']);
@ -426,7 +425,9 @@ HELP;
return false;
}
$table->addRow($user);
if (!empty($user)) {
$table->addRow($user);
}
$this->out($table->getTable());
return true;
@ -463,8 +464,7 @@ HELP;
}
}
$pconfig = \Friendica\DI::pConfig();
$values = $pconfig->load($user['uid'], $category);
$values = $this->pConfig->load($user['uid'], $category);
switch ($subCmd) {
case 'list':
@ -485,7 +485,7 @@ HELP;
throw new RuntimeException('Key does not exist');
}
$this->out($pconfig->get($user['uid'], $category, $key));
$this->out($this->pConfig->get($user['uid'], $category, $key));
break;
case 'set':
$value = $this->getArgument(5);
@ -499,12 +499,12 @@ HELP;
}
if (array_key_exists($category, $values) and
array_key_exists($key, $values[$category]) and
$values[$category][$key] == $value) {
array_key_exists($key, $values[$category]) and
$values[$category][$key] == $value) {
throw new RuntimeException('Value not changed');
}
$pconfig->set($user['uid'], $category, $key, $value);
$this->pConfig->set($user['uid'], $category, $key, $value);
break;
case 'delete':
if (!array_key_exists($category, $values)) {
@ -514,7 +514,7 @@ HELP;
throw new RuntimeException('Key does not exist');
}
$pconfig->delete($user['uid'], $category, $key);
$this->pConfig->delete($user['uid'], $category, $key);
break;
default:
$this->out($this->getHelp());

View File

@ -144,9 +144,9 @@ class Nav
* array 'userinfo' => Array of user information (name, icon)
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private static function getInfo(App $a)
private static function getInfo(App $a): array
{
$ssl_state = ((local_user()) ? true : false);
$ssl_state = (bool) local_user();
/*
* Our network is distributed, and as you visit friends some of the
@ -158,13 +158,27 @@ class Nav
$sitelocation = $myident . substr(DI::baseUrl()->get($ssl_state), strpos(DI::baseUrl()->get($ssl_state), '//') + 2);
// nav links: array of array('href', 'text', 'extra css classes', 'title')
$nav = [];
$nav = [
'admin' => null,
'apps' => null,
'community' => null,
'home' => null,
'events' => null,
'login' => null,
'logout' => null,
'langselector' => null,
'messages' => null,
'network' => null,
'notifications' => null,
'remote' => null,
'search' => null,
'usermenu' => [],
];
// Display login or logout
$nav['usermenu'] = [];
$userinfo = null;
// nav links: array of array('href', 'text', 'extra css classes', 'title')
if (Session::isAuthenticated()) {
$nav['logout'] = ['logout', DI::l10n()->t('Logout'), '', DI::l10n()->t('End this session')];
} else {
@ -297,13 +311,15 @@ class Nav
$banner = '<a href="https://friendi.ca"><img id="logo-img" src="images/friendica-32.png" alt="logo" /></a><span id="logo-text"><a href="https://friendi.ca">Friendica</a></span>';
}
Hook::callAll('nav_info', $nav);
return [
$nav_info = [
'banner' => $banner,
'nav' => $nav,
'sitelocation' => $sitelocation,
'nav' => $nav,
'banner' => $banner,
'userinfo' => $userinfo,
'userinfo' => $userinfo,
];
Hook::callAll('nav_info', $nav_info);
return $nav_info;
}
}

View File

@ -253,10 +253,15 @@ class PageInfo
// Fix for Mastodon where the mentions are in a different format
$body = preg_replace("~\[url=($URLSearchString)]([#!@])(.*?)\[/url]~is", '$2[url=$1]$3[/url]', $body);
preg_match("~(?<![!#@])\[url]($URLSearchString)\[/url]$~is", $body, $matches);
// Remove all hashtags and mentions
$body = preg_replace("/([#@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '', $body);
// Search for pure links
preg_match("/\[url\](https?:.*?)\[\/url\]/ism", $body, $matches);
if (!$matches) {
preg_match("~(?<![!#@])\[url=($URLSearchString)].*\[/url]$~is", $body, $matches);
// Search for links with descriptions
preg_match("/\[url\=(https?:.*?)\].*?\[\/url\]/ism", $body, $matches);
}
if (!$matches && $searchNakedUrls) {

View File

@ -50,9 +50,10 @@ use Friendica\Util\XML;
class BBCode
{
// Update this value to the current date whenever changes are made to BBCode::convert
const VERSION = '2021-04-24';
const VERSION = '2021-05-01';
const INTERNAL = 0;
const EXTERNAL = 1;
const API = 2;
const DIASPORA = 3;
const CONNECTORS = 4;
@ -61,7 +62,8 @@ class BBCode
const BACKLINK = 8;
const ACTIVITYPUB = 9;
const ANCHOR = '<br class="anchor">';
const TOP_ANCHOR = '<br class="top-anchor">';
const BOTTOM_ANCHOR = '<br class="button-anchor">';
/**
* Fetches attachment data that were generated the old way
*
@ -408,7 +410,7 @@ class BBCode
*/
public static function removeAttachment($body, $no_link_desc = false)
{
return preg_replace_callback("/\s*\[attachment (.*)\](.*?)\[\/attachment\]\s*/ism",
return preg_replace_callback("/\s*\[attachment (.*?)\](.*?)\[\/attachment\]\s*/ism",
function ($match) use ($no_link_desc) {
$attach_data = self::getAttachmentData($match[0]);
if (empty($attach_data['url'])) {
@ -1084,12 +1086,12 @@ class BBCode
'$avatar' => $attributes['avatar'],
'$author' => $attributes['author'],
'$link' => $attributes['link'],
'$link_title' => DI::l10n()->t('link to source'),
'$link_title' => DI::l10n()->t('Link to source'),
'$posted' => $attributes['posted'],
'$guid' => $attributes['guid'],
'$network_name' => ContactSelector::networkToName($network, $attributes['profile']),
'$network_icon' => ContactSelector::networkToIcon($network, $attributes['profile']),
'$content' => self::setMentions(trim($content), 0, $network) . self::ANCHOR,
'$content' => self::TOP_ANCHOR . self::setMentions(trim($content), 0, $network) . self::BOTTOM_ANCHOR,
]);
break;
}
@ -1103,20 +1105,14 @@ class BBCode
$text = DI::cache()->get($cache_key);
if (is_null($text)) {
$a = DI::app();
$curlResult = DI::httpRequest()->head($match[1], ['timeout' => DI::config()->get('system', 'xrd_timeout')]);
if ($curlResult->isSuccess()) {
$mimetype = $curlResult->getHeader('Content-Type');
} else {
$mimetype = '';
}
$stamp1 = microtime(true);
$ch = @curl_init($match[1]);
@curl_setopt($ch, CURLOPT_NOBODY, true);
@curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
@curl_setopt($ch, CURLOPT_USERAGENT, DI::httpRequest()->getUserAgent());
@curl_exec($ch);
$curl_info = @curl_getinfo($ch);
DI::profiler()->saveTimestamp($stamp1, "network");
if (substr($curl_info['content_type'], 0, 6) == 'image/') {
if (substr($mimetype, 0, 6) == 'image/') {
$text = "[url=" . $match[1] . ']' . $match[1] . "[/url]";
} else {
$text = "[url=" . $match[2] . ']' . $match[2] . "[/url]";
@ -1180,20 +1176,15 @@ class BBCode
return $text;
}
// Only fetch the header, not the content
$stamp1 = microtime(true);
$ch = @curl_init($match[1]);
@curl_setopt($ch, CURLOPT_NOBODY, true);
@curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
@curl_setopt($ch, CURLOPT_USERAGENT, DI::httpRequest()->getUserAgent());
@curl_exec($ch);
$curl_info = @curl_getinfo($ch);
DI::profiler()->saveTimestamp($stamp1, "network");
$curlResult = DI::httpRequest()->head($match[1], ['timeout' => DI::config()->get('system', 'xrd_timeout')]);
if ($curlResult->isSuccess()) {
$mimetype = $curlResult->getHeader('Content-Type');
} else {
$mimetype = '';
}
// if its a link to a picture then embed this picture
if (substr($curl_info['content_type'], 0, 6) == 'image/') {
if (substr($mimetype, 0, 6) == 'image/') {
$text = '[img]' . $match[1] . '[/img]';
} else {
if (!empty($match[3])) {
@ -1396,7 +1387,7 @@ class BBCode
// Handle attached links or videos
if ($simple_html == self::ACTIVITYPUB) {
$text = self::removeAttachment($text);
} elseif (!in_array($simple_html, [self::INTERNAL, self::CONNECTORS])) {
} elseif (!in_array($simple_html, [self::INTERNAL, self::EXTERNAL, self::CONNECTORS])) {
$text = self::removeAttachment($text, true);
} else {
$text = self::convertAttachment($text, $simple_html, $try_oembed);
@ -1406,9 +1397,9 @@ class BBCode
$text = str_replace('[nosmile]', '', $text);
// Replace non graphical smilies for external posts
if (!$nosmile && !$for_plaintext) {
$text = self::performWithEscapedTags($text, ['img'], function ($text) {
return Smilies::replace($text);
if (!$nosmile) {
$text = self::performWithEscapedTags($text, ['img'], function ($text) use ($simple_html, $for_plaintext) {
return Smilies::replace($text, ($simple_html != self::INTERNAL) || $for_plaintext);
});
}
@ -1730,7 +1721,7 @@ class BBCode
$text);
} elseif (!$simple_html) {
$text = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
'$1<a href="$2" class="userinfo mention" title="$3">$3</a>',
'$1<a href="$2" class="userinfo mention" title="$3"><bdi>$3</bdi></a>',
$text);
}
@ -1902,7 +1893,7 @@ class BBCode
$text = HTML::purify($text, $allowedIframeDomains);
return $text;
return trim($text);
}
/**

View File

@ -47,6 +47,7 @@ Commands:
addon Addon management
cache Manage node cache
config Edit site config
contact Contact management
createdoxygen Generate Doxygen headers
dbstructure Do database updates
docbloxerrorchecker Check the file tree for DocBlox errors
@ -78,6 +79,7 @@ HELP;
'addon' => Friendica\Console\Addon::class,
'cache' => Friendica\Console\Cache::class,
'config' => Friendica\Console\Config::class,
'contact' => Friendica\Console\Contact::class,
'createdoxygen' => Friendica\Console\CreateDoxygen::class,
'docbloxerrorchecker' => Friendica\Console\DocBloxErrorChecker::class,
'dbstructure' => Friendica\Console\DatabaseStructure::class,

View File

@ -35,7 +35,7 @@ interface IPConfig
* @param int $uid The user_id
* @param string $cat The category of the configuration value
*
* @return void
* @return array The loaded config array
* @see Cache
*
*/

View File

@ -68,6 +68,8 @@ class PreloadPConfig extends BasePConfig
// load the whole category out of the DB into the cache
$this->configCache->load($uid, $config);
return $config;
}
/**

View File

@ -134,7 +134,7 @@ class Search
$profiles = $results['profiles'] ?? [];
foreach ($profiles as $profile) {
$profile_url = $profile['url'] ?? '';
$profile_url = $profile['profile_url'] ?? '';
$contactDetails = Contact::getByURLForUser($profile_url, local_user());
$result = new ContactResult(

View File

@ -239,6 +239,14 @@ abstract class DI
return self::$dice->create(Factory\Api\Mastodon\Account::class);
}
/**
* @return Factory\Api\Mastodon\Application
*/
public static function mstdnApplication()
{
return self::$dice->create(Factory\Api\Mastodon\Application::class);
}
/**
* @return Factory\Api\Mastodon\Attachment
*/
@ -247,6 +255,14 @@ abstract class DI
return self::$dice->create(Factory\Api\Mastodon\Attachment::class);
}
/**
* @return Factory\Api\Mastodon\Card
*/
public static function mstdnCard()
{
return self::$dice->create(Factory\Api\Mastodon\Card::class);
}
/**
* @return Factory\Api\Mastodon\Emoji
*/
@ -295,6 +311,14 @@ abstract class DI
return self::$dice->create(Factory\Api\Mastodon\Status::class);
}
/**
* @return Factory\Api\Mastodon\ListEntity
*/
public static function mstdnList()
{
return self::$dice->create(Factory\Api\Mastodon\ListEntity::class);
}
/**
* @return Factory\Api\Mastodon\Mention
*/
@ -303,6 +327,14 @@ abstract class DI
return self::$dice->create(Factory\Api\Mastodon\Mention::class);
}
/**
* @return Factory\Api\Mastodon\Notification
*/
public static function mstdnNotification()
{
return self::$dice->create(Factory\Api\Mastodon\Notification::class);
}
/**
* @return Factory\Api\Mastodon\Tag
*/

View File

@ -0,0 +1,49 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Factory\Api\Mastodon;
use Friendica\BaseFactory;
use Friendica\Database\DBA;
class Application extends BaseFactory
{
/**
* @param int $id Application ID
*/
public function createFromApplicationId(int $id)
{
$application = DBA::selectFirst('application', ['client_id', 'client_secret', 'id', 'name', 'redirect_uri', 'website'], ['id' => $id]);
if (!DBA::isResult($application)) {
return [];
}
$object = new \Friendica\Object\Api\Mastodon\Application(
$application['name'],
$application['client_id'],
$application['client_secret'],
$application['id'],
$application['redirect_uri'],
$application['website']);
return $object->toArray();
}
}

View File

@ -23,9 +23,12 @@ namespace Friendica\Factory\Api\Mastodon;
use Friendica\App\BaseURL;
use Friendica\BaseFactory;
use Friendica\DI;
use Friendica\Model\Photo;
use Friendica\Network\HTTPException;
use Friendica\Model\Post;
use Friendica\Repository\ProfileField;
use Friendica\Util\Images;
use Friendica\Util\Proxy;
use Psr\Log\LoggerInterface;
@ -56,7 +59,7 @@ class Attachment extends BaseFactory
public function createFromUriId(int $uriId)
{
$attachments = [];
foreach (Post\Media::getByURIId($uriId) as $attachment) {
foreach (Post\Media::getByURIId($uriId, [Post\Media::AUDIO, Post\Media::VIDEO, Post\Media::IMAGE]) as $attachment) {
$filetype = !empty($attachment['mimetype']) ? strtolower(substr($attachment['mimetype'], 0, strpos($attachment['mimetype'], '/'))) : '';
@ -93,4 +96,36 @@ class Attachment extends BaseFactory
return $attachments;
}
/**
* @param int $id id of the photo
* @return array
* @throws HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public function createFromPhoto(int $id)
{
$photo = Photo::selectFirst(['resource-id', 'uid', 'id', 'title', 'type'], ['id' => $id]);
if (empty($photo)) {
return null;
}
$attachment = ['id' => $photo['id'], 'description' => $photo['title']];
$phototypes = Images::supportedTypes();
$ext = $phototypes[$photo['type']];
$url = DI::baseUrl() . '/photo/' . $photo['resource-id'] . '-0.' . $ext;
$preview = Photo::selectFirst(['scale'], ["`resource-id` = ? AND `uid` = ? AND `scale` > ?", $photo['resource-id'], $photo['uid'], 0], ['order' => ['scale']]);
if (empty($scale)) {
$preview_url = DI::baseUrl() . '/photo/' . $photo['resource-id'] . '-' . $preview['scale'] . '.' . $ext;
} else {
$preview_url = '';
}
$object = new \Friendica\Object\Api\Mastodon\Attachment($attachment, 'image', $url, $preview_url, '');
return $object->toArray();
}
}

View File

@ -0,0 +1,80 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Factory\Api\Mastodon;
use Friendica\BaseFactory;
use Friendica\Content\Text\BBCode;
use Friendica\Model\Post;
use Friendica\Network\HTTPException;
use Friendica\Util\Strings;
class Card extends BaseFactory
{
/**
* @param int $uriId Uri-ID of the item
* @return \Friendica\Object\Api\Mastodon\Card
* @throws HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public function createFromUriId(int $uriId)
{
$item = Post::selectFirst(['body'], ['uri-id' => $uriId]);
if (!empty($item['body'])) {
$data = BBCode::getAttachmentData($item['body']);
} else {
$data = [];
}
foreach (Post\Media::getByURIId($uriId, [Post\Media::HTML]) as $attached) {
if ((empty($data['url']) || Strings::compareLink($data['url'], $attached['url'])) &&
(!empty($attached['description']) || !empty($attached['image']) || !empty($attached['preview']))) {
$parts = parse_url($attached['url']);
if (!empty($parts['scheme']) && !empty($parts['host'])) {
if (empty($attached['publisher-name'])) {
$attached['publisher-name'] = $parts['host'];
}
if (empty($attached['publisher-url']) || empty(parse_url($attached['publisher-url'], PHP_URL_SCHEME))) {
$attached['publisher-url'] = $parts['scheme'] . '://' . $parts['host'];
if (!empty($parts['port'])) {
$attached['publisher-url'] .= ':' . $parts['port'];
}
}
}
$data['url'] = $attached['url'];
$data['title'] = $attached['name'];
$data['description'] = $attached['description'];
$data['type'] = 'link';
$data['author_name'] = $attached['author-name'];
$data['author_url'] = $attached['author-url'];
$data['provider_name'] = $attached['publisher-name'];
$data['provider_url'] = $attached['publisher-url'];
$data['image'] = $attached['preview'];
$data['width'] = $attached['preview-width'];
$data['height'] = $attached['preview-height'];
}
}
return new \Friendica\Object\Api\Mastodon\Card($data);
}
}

View File

@ -35,4 +35,31 @@ class Error extends BaseFactory
System::jsonError(404, $errorobj->toArray());
}
public function UnprocessableEntity(string $error = '')
{
$error = $error ?: DI::l10n()->t('Unprocessable Entity');
$error_description = '';
$errorobj = New \Friendica\Object\Api\Mastodon\Error($error, $error_description);
System::jsonError(422, $errorobj->toArray());
}
public function Unauthorized(string $error = '')
{
$error = $error ?: DI::l10n()->t('Unauthorized');
$error_description = '';
$errorobj = New \Friendica\Object\Api\Mastodon\Error($error, $error_description);
System::jsonError(401, $errorobj->toArray());
}
public function InternalError(string $error = '')
{
$error = $error ?: DI::l10n()->t('Internal Server Error');
$error_description = '';
$errorobj = New \Friendica\Object\Api\Mastodon\Error($error, $error_description);
System::jsonError(500, $errorobj->toArray());
}
}

View File

@ -0,0 +1,34 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Factory\Api\Mastodon;
use Friendica\BaseFactory;
use Friendica\Database\DBA;
class ListEntity extends BaseFactory
{
public function createFromGroupId(int $id)
{
$group = DBA::selectFirst('group', ['name'], ['id' => $id, 'deleted' => false]);
return new \Friendica\Object\Api\Mastodon\ListEntity($id, $group['name'] ?? '', 'list');
}
}

View File

@ -0,0 +1,89 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Factory\Api\Mastodon;
use Friendica\BaseFactory;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Notification as ModelNotification;
class Notification extends BaseFactory
{
public function createFromNotifyId(int $id)
{
$notification = DBA::selectFirst('notify', [], ['id' => $id]);
if (!DBA::isResult($notification)) {
return null;
}
$cid = Contact::getIdForURL($notification['url'], 0, false);
if (empty($cid)) {
return null;
}
/*
follow = Someone followed you
follow_request = Someone requested to follow you
mention = Someone mentioned you in their status
reblog = Someone boosted one of your statuses
favourite = Someone favourited one of your statuses
poll = A poll you have voted in or created has ended
status = Someone you enabled notifications for has posted a status
*/
switch ($notification['type']) {
case ModelNotification\Type::INTRO:
$type = 'follow_request';
break;
case ModelNotification\Type::WALL:
case ModelNotification\Type::COMMENT:
case ModelNotification\Type::MAIL:
case ModelNotification\Type::TAG_SELF:
case ModelNotification\Type::POKE:
$type = 'mention';
break;
case ModelNotification\Type::SHARE:
$type = 'status';
break;
default:
return null;
}
$account = DI::mstdnAccount()->createFromContactId($cid);
if (!empty($notification['uri-id'])) {
try {
$status = DI::mstdnStatus()->createFromUriId($notification['uri-id'], $notification['uid']);
} catch (\Throwable $th) {
$status = null;
}
} else {
$status = null;
}
return new \Friendica\Object\Api\Mastodon\Notification($id, $type, $notification['date'], $account, $status);
}
}

View File

@ -23,6 +23,7 @@ namespace Friendica\Factory\Api\Mastodon;
use Friendica\App\BaseURL;
use Friendica\BaseFactory;
use Friendica\Content\ContactSelector;
use Friendica\Content\Text\BBCode;
use Friendica\Database\DBA;
use Friendica\DI;
@ -60,9 +61,9 @@ class Status extends BaseFactory
*/
public function createFromUriId(int $uriId, $uid = 0)
{
$fields = ['uri-id', 'uid', 'author-id', 'starred', 'app', 'title', 'body', 'raw-body', 'created',
$fields = ['uri-id', 'uid', 'author-id', 'author-link', 'starred', 'app', 'title', 'body', 'raw-body', 'created', 'network',
'thr-parent-id', 'parent-author-id', 'language', 'uri', 'plink', 'private', 'vid', 'gravity'];
$item = Post::selectFirst($fields, ['uri-id' => $uriId, 'uid' => $uid]);
$item = Post::selectFirst($fields, ['uri-id' => $uriId, 'uid' => [0, $uid]], ['order' => ['uid' => true]]);
if (!$item) {
throw new HTTPException\NotFoundException('Item with URI ID ' . $uriId . 'not found' . ($uid ? ' for user ' . $uid : '.'));
}
@ -70,32 +71,46 @@ class Status extends BaseFactory
$account = DI::mstdnAccount()->createFromContactId($item['author-id']);
$counts = new \Friendica\Object\Api\Mastodon\Status\Counts(
Post::count(['thr-parent-id' => $uriId, 'uid' => $uid, 'gravity' => GRAVITY_COMMENT]),
Post::count(['thr-parent-id' => $uriId, 'uid' => $uid, 'gravity' => GRAVITY_ACTIVITY, 'vid' => Verb::getID(Activity::ANNOUNCE)]),
Post::count(['thr-parent-id' => $uriId, 'uid' => $uid, 'gravity' => GRAVITY_ACTIVITY, 'vid' => Verb::getID(Activity::LIKE)])
Post::count(['thr-parent-id' => $uriId, 'gravity' => GRAVITY_COMMENT], [], false),
Post::count(['thr-parent-id' => $uriId, 'gravity' => GRAVITY_ACTIVITY, 'vid' => Verb::getID(Activity::ANNOUNCE)], [], false),
Post::count(['thr-parent-id' => $uriId, 'gravity' => GRAVITY_ACTIVITY, 'vid' => Verb::getID(Activity::LIKE)], [], false)
);
$userAttributes = new \Friendica\Object\Api\Mastodon\Status\UserAttributes(
Post::exists(['thr-parent-id' => $uriId, 'uid' => $uid, 'origin' => true, 'gravity' => GRAVITY_ACTIVITY, 'vid' => Verb::getID(Activity::LIKE)]),
Post::exists(['thr-parent-id' => $uriId, 'uid' => $uid, 'origin' => true, 'gravity' => GRAVITY_ACTIVITY, 'vid' => Verb::getID(Activity::ANNOUNCE)]),
Post\ThreadUser::getIgnored($uriId, $item['uid']),
Post\ThreadUser::getIgnored($uriId, $uid),
(bool)$item['starred'],
Post\ThreadUser::getPinned($uriId, $item['uid'])
Post\ThreadUser::getPinned($uriId, $uid)
);
$sensitive = DBA::exists('tag-view', ['uri-id' => $uriId, 'name' => 'nsfw']);
$application = new \Friendica\Object\Api\Mastodon\Application($item['app'] ?? '');
$mentions = DI::mstdnMention()->createFromUriId($uriId);
$tags = DI::mstdnTag()->createFromUriId($uriId);
$data = BBCode::getAttachmentData($item['body']);
$card = new \Friendica\Object\Api\Mastodon\Card($data);
$application = new \Friendica\Object\Api\Mastodon\Application($item['app'] ?: ContactSelector::networkToName($item['network'], $item['author-link']));
$mentions = DI::mstdnMention()->createFromUriId($uriId);
$tags = DI::mstdnTag()->createFromUriId($uriId);
$card = DI::mstdnCard()->createFromUriId($uriId);
$attachments = DI::mstdnAttachment()->createFromUriId($uriId);
$shared = BBCode::fetchShareAttributes($item['body']);
if (!empty($shared['guid'])) {
$shared_item = Post::selectFirst(['uri-id', 'plink'], ['guid' => $shared['guid']]);
$shared_uri_id = $shared_item['uri-id'] ?? 0;
$mentions = array_merge($mentions, DI::mstdnMention()->createFromUriId($shared_uri_id));
$tags = array_merge($tags, DI::mstdnTag()->createFromUriId($shared_uri_id));
$attachments = array_merge($attachments, DI::mstdnAttachment()->createFromUriId($shared_uri_id));
if (empty($card->toArray())) {
$card = DI::mstdnCard()->createFromUriId($shared_uri_id);
}
}
if ($item['vid'] == Verb::getID(Activity::ANNOUNCE)) {
$reshare = $this->createFromUriId($item['thr-parent-id'], $uid)->toArray();
$reshared_item = Post::selectFirst(['title', 'body'], ['uri-id' => $item['thr-parent-id'], 'uid' => $uid]);
$reshared_item = Post::selectFirst(['title', 'body'], ['uri-id' => $item['thr-parent-id'], 'uid' => [0, $uid]]);
$item['title'] = $reshared_item['title'] ?? $item['title'];
$item['body'] = $reshared_item['body'] ?? $item['body'];
} else {

View File

@ -235,7 +235,7 @@ class APContact
unset($parts['path']);
if (empty($apcontact['addr'])) {
if (!empty($apcontact['nick'])) {
if (!empty($apcontact['nick']) && is_array($parts)) {
$apcontact['addr'] = $apcontact['nick'] . '@' . str_replace('//', '', Network::unparseURL($parts));
} else {
$apcontact['addr'] = '';

View File

@ -264,7 +264,7 @@ class Contact
$condition = ['`alias` IN (?, ?, ?) AND `uid` = ? AND NOT `deleted`', $url, Strings::normaliseLink($url), $ssl_url, $uid];
$contact = DBA::selectFirst('contact', $fields, $condition, $options);
}
if (!DBA::isResult($contact)) {
return [];
}
@ -307,7 +307,7 @@ class Contact
}
$contact = self::getByURL($url, $update, $fields);
if (!empty($contact['id'])) {
if (!empty($contact['id'])) {
$contact['cid'] = 0;
$contact['zid'] = $contact['id'];
}
@ -1274,7 +1274,7 @@ class Contact
*
* @param string $contact_url Contact URL
* @param bool $thread_mode
* @param int $update Update mode
* @param int $update Update mode
* @param int $parent Item parent ID for the update mode
* @return string posts in HTML
* @throws \Exception
@ -1289,7 +1289,7 @@ class Contact
*
* @param int $cid Contact ID
* @param bool $thread_mode
* @param int $update Update mode
* @param int $update Update mode
* @param int $parent Item parent ID for the update mode
* @return string posts in HTML
* @throws \Exception
@ -1347,7 +1347,7 @@ class Contact
$o = '';
}
if ($thread_mode) {
if ($thread_mode) {
$items = Post::toArray(Post::selectForUser(local_user(), ['uri-id', 'gravity', 'parent-uri-id', 'thr-parent-id', 'author-id'], $condition, $params));
$o .= conversation($a, $items, 'contacts', $update, false, 'commented', local_user());
@ -1607,12 +1607,12 @@ class Contact
$avatar['size'] = 48;
$default = self::DEFAULT_AVATAR_MICRO;
break;
case Proxy::SIZE_THUMB:
$avatar['size'] = 80;
$default = self::DEFAULT_AVATAR_THUMB;
break;
case Proxy::SIZE_SMALL:
default:
$avatar['size'] = 300;
@ -1720,7 +1720,7 @@ class Contact
$contact['thumb'] ?? '',
$contact['micro'] ?? '',
];
foreach ($data as $image_uri) {
$image_rid = Photo::ridFromURI($image_uri);
if ($image_rid && !Photo::exists(['resource-id' => $image_rid, 'uid' => $uid])) {
@ -2027,7 +2027,7 @@ class Contact
if (Contact\Relation::isDiscoverable($ret['url'])) {
Worker::add(PRIORITY_LOW, 'ContactDiscovery', $ret['url']);
}
// Update the public contact
if ($uid != 0) {
$contact = self::getByURL($ret['url'], false, ['id']);
@ -2763,11 +2763,12 @@ class Contact
*
* @param string $search Name or nick
* @param string $mode Search mode (e.g. "community")
* @param int $uid User ID
*
* @return array with search results
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function searchByName($search, $mode = '')
public static function searchByName(string $search, string $mode = '', int $uid = 0)
{
if (empty($search)) {
return [];
@ -2800,7 +2801,7 @@ class Contact
NOT `failed` AND `uid` = ? AND
(`addr` LIKE ? OR `name` LIKE ? OR `nick` LIKE ?) $extra_sql
ORDER BY `nurl` DESC LIMIT 1000",
Protocol::DFRN, Protocol::ACTIVITYPUB, $ostatus, $diaspora, 0, $search, $search, $search
Protocol::DFRN, Protocol::ACTIVITYPUB, $ostatus, $diaspora, $uid, $search, $search, $search
);
$contacts = DBA::toArray($results);

View File

@ -21,7 +21,6 @@
namespace Friendica\Model;
use Friendica\Content\PageInfo;
use Friendica\Content\Text\BBCode;
use Friendica\Content\Text\HTML;
use Friendica\Core\Hook;
@ -173,7 +172,7 @@ class Item
Logger::info('Updating per single row method', ['fields' => $fields, 'condition' => $condition]);
$items = Post::select(['id', 'origin', 'uri-id', 'uid'], $condition);
$items = Post::select(['id', 'origin', 'uri-id', 'uid', 'author-network'], $condition);
$notify_items = [];
@ -181,9 +180,14 @@ class Item
if (!empty($fields['body'])) {
Post\Media::insertFromAttachmentData($item['uri-id'], $fields['body']);
if ($item['author-network'] != Protocol::DFRN) {
Post\Media::insertFromRelevantUrl($item['uri-id'], $fields['body']);
}
$content_fields = ['raw-body' => trim($fields['raw-body'] ?? $fields['body'])];
// Remove all media attachments from the body and store them in the post-media table
// @todo On shared postings (Diaspora style and commented reshare) don't fetch content from the shared part
$content_fields['raw-body'] = Post\Media::insertFromBody($item['uri-id'], $content_fields['raw-body']);
$content_fields['raw-body'] = self::setHashtags($content_fields['raw-body']);
}
@ -959,8 +963,20 @@ class Item
self::setOwnerforResharedItem($item);
}
if (isset($item['attachments'])) {
foreach ($item['attachments'] as $attachment) {
$attachment['uri-id'] = $item['uri-id'];
Post\Media::insert($attachment);
}
unset($item['attachments']);
}
Post\Media::insertFromAttachmentData($item['uri-id'], $item['body']);
if (!DBA::exists('contact', ['id' => $item['author-id'], 'network' => Protocol::DFRN])) {
Post\Media::insertFromRelevantUrl($item['uri-id'], $item['body']);
}
// Remove all media attachments from the body and store them in the post-media table
$item['raw-body'] = Post\Media::insertFromBody($item['uri-id'], $item['raw-body']);
$item['raw-body'] = self::setHashtags($item['raw-body']);
@ -1706,7 +1722,7 @@ class Item
return ("[bookmark=" . str_replace("#", "&num;", $match[1]) . "]" . str_replace("#", "&num;", $match[2]) . "[/bookmark]");
}, $body);
$body = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
$body = preg_replace_callback("/\[attachment (.*?)\](.*?)\[\/attachment\]/ism",
function ($match) {
return ("[attachment " . str_replace("#", "&num;", $match[1]) . "]" . $match[2] . "[/attachment]");
}, $body);
@ -2639,10 +2655,10 @@ class Item
unset($hook_data);
}
$orig_body = $item['body'];
$item['body'] = preg_replace("/\s*\[attachment .*\].*?\[\/attachment\]\s*/ism", '', $item['body']);
$body = $item['body'] ?? '';
$item['body'] = preg_replace("/\s*\[attachment .*?\].*?\[\/attachment\]\s*/ism", "\n", $item['body']);
self::putInCache($item);
$item['body'] = $orig_body;
$item['body'] = $body;
$s = $item["rendered-html"];
$hook_data = [
@ -2666,19 +2682,23 @@ class Item
if (!empty($shared['guid'])) {
$shared_item = Post::selectFirst(['uri-id', 'plink'], ['guid' => $shared['guid']]);
$shared_uri_id = $shared_item['uri-id'] ?? 0;
$shared_plink = $shared_item['plink'] ?? '';
$shared_links = [strtolower($shared_item['plink'] ?? '')];
$attachments = Post\Media::splitAttachments($shared_uri_id, $shared['guid']);
$s = self::addVisualAttachments($attachments, $item, $s, true);
$s = self::addLinkAttachment($attachments, $item, $s, true, '');
$s = self::addLinkAttachment($attachments, $body, $s, true, []);
$s = self::addNonVisualAttachments($attachments, $item, $s, true);
$shared_links = array_merge($shared_links, array_column($attachments['visual'], 'url'));
$shared_links = array_merge($shared_links, array_column($attachments['link'], 'url'));
$shared_links = array_merge($shared_links, array_column($attachments['additional'], 'url'));
$body = preg_replace("/\s*\[share .*?\].*?\[\/share\]\s*/ism", '', $body);
} else {
$shared_uri_id = 0;
$shared_plink = '';
$shared_links = [];
}
$attachments = Post\Media::splitAttachments($item['uri-id'], $item['guid']);
$attachments = Post\Media::splitAttachments($item['uri-id'], $item['guid'], $shared_links);
$s = self::addVisualAttachments($attachments, $item, $s, false);
$s = self::addLinkAttachment($attachments, $item, $s, false, $shared_plink);
$s = self::addLinkAttachment($attachments, $body, $s, false, $shared_links);
$s = self::addNonVisualAttachments($attachments, $item, $s, false);
// Map.
@ -2712,6 +2732,12 @@ class Item
*/
public static function containsLink(string $body, string $url)
{
// Make sure that for example site parameters aren't used when testing if the link is contained in the body
$urlparts = parse_url($url);
unset($urlparts['query']);
unset($urlparts['fragment']);
$url = Network::unparseURL($urlparts);
if (strpos($body, $url)) {
return true;
}
@ -2738,6 +2764,7 @@ class Item
$leading = '';
$trailing = '';
// @todo In the future we should make a single for the template engine with all media in it. This allows more flexibilty.
foreach ($attachments['visual'] as $attachment) {
if (self::containsLink($item['body'], $attachment['url'])) {
continue;
@ -2794,13 +2821,18 @@ class Item
'attachment' => $attachment,
],
]);
$trailing .= $media;
// On Diaspora posts the attached pictures are leading
if ($item['network'] == Protocol::DIASPORA) {
$leading .= $media;
} else {
$trailing .= $media;
}
}
}
if ($shared) {
$content = str_replace(BBCode::ANCHOR, '<div class="body-attach">' . $leading . '<div class="clear"></div></div>' . BBCode::ANCHOR, $content);
$content = str_replace(BBCode::ANCHOR, BBCode::ANCHOR . '<div class="body-attach">' . $trailing . '<div class="clear"></div></div>', $content);
$content = str_replace(BBCode::TOP_ANCHOR, '<div class="body-attach">' . $leading . '<div class="clear"></div></div>' . BBCode::TOP_ANCHOR, $content);
$content = str_replace(BBCode::BOTTOM_ANCHOR, '<div class="body-attach">' . $trailing . '<div class="clear"></div></div>' . BBCode::BOTTOM_ANCHOR, $content);
} else {
if ($leading != '') {
$content = '<div class="body-attach">' . $leading . '<div class="clear"></div></div>' . $content;
@ -2819,12 +2851,13 @@ class Item
* Add link attachment to the content
*
* @param array $attachments
* @param array $item
* @param string $body
* @param string $content
* @param bool $shared
* @param array $ignore_links A list of URLs to ignore
* @return string modified content
*/
private static function addLinkAttachment(array $attachments, array $item, string $content, bool $shared, string $ignore_link)
private static function addLinkAttachment(array $attachments, string $body, string $content, bool $shared, array $ignore_links)
{
$stamp1 = microtime(true);
// @ToDo Check only for audio and video
@ -2832,14 +2865,23 @@ class Item
if (!empty($attachments['link'])) {
foreach ($attachments['link'] as $link) {
if (!Strings::compareLink($link['url'], $ignore_link)) {
$found = false;
foreach ($ignore_links as $ignore_link) {
if (Strings::compareLink($link['url'], $ignore_link)) {
$found = true;
}
}
// @todo Judge between the links to use the one with most information
if (!$found && (empty($attachment) || !empty($link['author-name']) ||
(empty($attachment['name']) && !empty($link['name'])) ||
(empty($attachment['description']) && !empty($link['description'])) ||
(empty($attachment['preview']) && !empty($link['preview'])))) {
$attachment = $link;
}
}
}
if (!empty($attachment)) {
$footer = '';
$data = [
'after' => '',
'author_name' => $attachment['author-name'] ?? '',
@ -2861,17 +2903,58 @@ class Item
$data['preview'] = $attachment['preview'] ?? '';
}
}
} elseif (preg_match("/.*(\[attachment.*?\].*?\[\/attachment\]).*/ism", $item['body'], $match)) {
$footer = $match[1];
$data = [];
if (!empty($data['description']) && !empty($content)) {
similar_text($data['description'], $content, $percent);
} else {
$percent = 0;
}
if (!empty($data['description']) && (($data['title'] == $data['description']) || ($percent > 95) || (strpos($content, $data['description']) !== false))) {
$data['description'] = '';
}
if (($data['author_name'] ?? '') == ($data['provider_name'] ?? '')) {
$data['author_name'] = '';
}
if (($data['author_url'] ?? '') == ($data['provider_url'] ?? '')) {
$data['author_url'] = '';
}
} elseif (preg_match("/.*(\[attachment.*?\].*?\[\/attachment\]).*/ism", $body, $match)) {
$data = BBCode::getAttachmentData($match[1]);
}
DI::profiler()->saveTimestamp($stamp1, 'rendering');
if (!empty($footer) || !empty($data)) {
// @todo Use a template
$rendered = BBCode::convertAttachment($footer, BBCode::INTERNAL, false, $data);
if (isset($data['url']) && !in_array($data['url'], $ignore_links)) {
if (!empty($data['description']) || !empty($data['image']) || !empty($data['preview'])) {
$parts = parse_url($data['url']);
if (!empty($parts['scheme']) && !empty($parts['host'])) {
if (empty($data['provider_name'])) {
$data['provider_name'] = $parts['host'];
}
if (empty($data['provider_url']) || empty(parse_url($data['provider_url'], PHP_URL_SCHEME))) {
$data['provider_url'] = $parts['scheme'] . '://' . $parts['host'];
if (!empty($parts['port'])) {
$data['provider_url'] .= ':' . $parts['port'];
}
}
}
// @todo Use a template
$rendered = BBCode::convertAttachment('', BBCode::INTERNAL, false, $data);
} elseif (!self::containsLink($content, $data['url'])) {
$rendered = Renderer::replaceMacros(Renderer::getMarkupTemplate('content/link.tpl'), [
'$url' => $data['url'],
'$title' => $data['title'],
]);
} else {
return $content;
}
if ($shared) {
return str_replace(BBCode::ANCHOR, BBCode::ANCHOR . $rendered, $content);
return str_replace(BBCode::BOTTOM_ANCHOR, BBCode::BOTTOM_ANCHOR . $rendered, $content);
} else {
return $content . $rendered;
}
@ -2933,18 +3016,19 @@ class Item
'href' => "display/" . $item['guid'],
'orig' => "display/" . $item['guid'],
'title' => DI::l10n()->t('View on separate page'),
'orig_title' => DI::l10n()->t('view on separate page'),
'orig_title' => DI::l10n()->t('View on separate page'),
];
if (!empty($item['plink'])) {
$ret["href"] = DI::baseUrl()->remove($item['plink']);
$ret["title"] = DI::l10n()->t('link to source');
$ret['href'] = DI::baseUrl()->remove($item['plink']);
$ret['title'] = DI::l10n()->t('Link to source');
}
} elseif (!empty($item['plink']) && ($item['private'] != self::PRIVATE)) {
$ret = [
'href' => $item['plink'],
'orig' => $item['plink'],
'title' => DI::l10n()->t('link to source'),
'title' => DI::l10n()->t('Link to source'),
'orig_title' => DI::l10n()->t('Link to source'),
];
} else {
$ret = [];
@ -3182,4 +3266,41 @@ class Item
return true;
}
/**
* Improve the data in shared posts
*
* @param array $item
* @return string body
*/
public static function improveSharedDataInBody(array $item)
{
$shared = BBCode::fetchShareAttributes($item['body']);
if (empty($shared['link'])) {
return $item['body'];
}
$id = self::fetchByLink($shared['link']);
Logger::info('Fetched shared post', ['uri-id' => $item['uri-id'], 'id' => $id, 'author' => $shared['profile'], 'url' => $shared['link'], 'guid' => $shared['guid'], 'callstack' => System::callstack()]);
if (!$id) {
return $item['body'];
}
$shared_item = Post::selectFirst(['author-name', 'author-link', 'author-avatar', 'plink', 'created', 'guid', 'title', 'body'], ['id' => $id]);
if (!DBA::isResult($shared_item)) {
return $item['body'];
}
$shared_content = BBCode::getShareOpeningTag($shared_item['author-name'], $shared_item['author-link'], $shared_item['author-avatar'], $shared_item['plink'], $shared_item['created'], $shared_item['guid']);
if (!empty($shared_item['title'])) {
$shared_content .= '[h3]'.$shared_item['title'].'[/h3]'."\n";
}
$shared_content .= $shared_item['body'];
$item['body'] = preg_replace("/\[share.*?\](.*)\[\/share\]/ism", $shared_content . '[/share]', $item['body']);
Logger::info('New shared data', ['uri-id' => $item['uri-id'], 'id' => $id, 'shared_item' => $shared_item]);
return $item['body'];
}
}

View File

@ -726,18 +726,35 @@ class Photo
* Then set the permissions to public.
*/
$fields = ['allow_cid' => $str_contact_allow, 'allow_gid' => $str_group_allow,
'deny_cid' => $str_contact_deny, 'deny_gid' => $str_group_deny,
'accessible' => DI::pConfig()->get($uid, 'system', 'accessible-photos', false)];
$condition = ['resource-id' => $image_rid, 'uid' => $uid];
Logger::info('Set permissions', ['condition' => $condition, 'permissions' => $fields]);
Photo::update($fields, $condition);
self::setPermissionForRessource($image_rid, $uid, $str_contact_allow, $str_group_allow, $str_contact_deny, $str_group_deny);
}
return true;
}
/**
* Add permissions to photo ressource
* @todo mix with previous photo permissions
*
* @param string $image_rid
* @param integer $uid
* @param string $str_contact_allow
* @param string $str_group_allow
* @param string $str_contact_deny
* @param string $str_group_deny
* @return void
*/
public static function setPermissionForRessource(string $image_rid, int $uid, string $str_contact_allow, string $str_group_allow, string $str_contact_deny, string $str_group_deny)
{
$fields = ['allow_cid' => $str_contact_allow, 'allow_gid' => $str_group_allow,
'deny_cid' => $str_contact_deny, 'deny_gid' => $str_group_deny,
'accessible' => DI::pConfig()->get($uid, 'system', 'accessible-photos', false)];
$condition = ['resource-id' => $image_rid, 'uid' => $uid];
Logger::info('Set permissions', ['condition' => $condition, 'permissions' => $fields]);
Photo::update($fields, $condition);
}
/**
* Strips known picture extensions from picture links
*

View File

@ -127,12 +127,13 @@ class Post
* Check if post data exists
*
* @param array $condition array of fields for condition
* @param bool $user_mode true = post-user-view, false = post-view
*
* @return boolean Are there rows for that condition?
* @throws \Exception
*/
public static function exists($condition) {
return DBA::exists('post-user-view', $condition);
public static function exists($condition, bool $user_mode = true) {
return DBA::exists($user_mode ? 'post-user-view' : 'post-view', $condition);
}
/**
@ -140,6 +141,7 @@ class Post
*
* @param array $condition array of fields for condition
* @param array $params Array of several parameters
* @param bool $user_mode true = post-user-view, false = post-view
*
* @return int
*
@ -151,9 +153,9 @@ class Post
* $count = Post::count($condition);
* @throws \Exception
*/
public static function count(array $condition = [], array $params = [])
public static function count(array $condition = [], array $params = [], bool $user_mode = true)
{
return DBA::count('post-user-view', $condition, $params);
return DBA::count($user_mode ? 'post-user-view' : 'post-view', $condition, $params);
}
/**
@ -265,13 +267,14 @@ class Post
* @param array $selected Array of selected fields, empty for all
* @param array $condition Array of fields for condition
* @param array $params Array of several parameters
* @param bool $user_mode true = post-user-view, false = post-view
*
* @return boolean|object
* @throws \Exception
*/
public static function select(array $selected = [], array $condition = [], $params = [])
public static function select(array $selected = [], array $condition = [], $params = [], bool $user_mode = true)
{
return self::selectView('post-user-view', $selected, $condition, $params);
return self::selectView($user_mode ? 'post-user-view' : 'post-view', $selected, $condition, $params);
}
/**

View File

@ -112,6 +112,10 @@ class Delayed
*/
public static function publish(array $item, int $notify = 0, array $taglist = [], array $attachments = [], bool $unprepared = false, string $uri = '')
{
if (!empty($attachments)) {
$item['attachments'] = $attachments;
}
if ($unprepared) {
$_SESSION['authenticated'] = true;
$_SESSION['uid'] = $item['uid'];
@ -157,11 +161,6 @@ class Delayed
foreach ($taglist as $tag) {
Tag::store($feeditem['uri-id'], Tag::HASHTAG, $tag);
}
foreach ($attachments as $attachment) {
$attachment['uri-id'] = $feeditem['uri-id'];
Media::insert($attachment);
}
}
return $id;

View File

@ -21,15 +21,17 @@
namespace Friendica\Model\Post;
use Friendica\Content\PageInfo;
use Friendica\Content\Text\BBCode;
use Friendica\Core\Logger;
use Friendica\Core\System;
use Friendica\Database\Database;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Item;
use Friendica\Model\Post;
use Friendica\Util\Images;
use Friendica\Util\ParseUrl;
use Friendica\Util\Strings;
/**
* Class Media
@ -64,6 +66,11 @@ class Media
return;
}
if (DBA::exists('post-media', ['uri-id' => $media['uri-id'], 'preview' => $media['url']])) {
Logger::info('Media already exists as preview', ['uri-id' => $media['uri-id'], 'url' => $media['url'], 'callstack' => System::callstack()]);
return;
}
// "document" has got the lowest priority. So when the same file is both attached as document
// and embedded as picture then we only store the picture or replace the document
$found = DBA::selectFirst('post-media', ['type'], ['uri-id' => $media['uri-id'], 'url' => $media['url']]);
@ -281,7 +288,13 @@ class Media
public static function insertFromBody(int $uriid, string $body)
{
// Simplify image codes
$body = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $body);
$unshared_body = $body = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $body);
// Only remove the shared data from "real" reshares
$shared = BBCode::fetchShareAttributes($body);
if (!empty($shared['guid'])) {
$unshared_body = preg_replace("/\s*\[share .*?\].*?\[\/share\]\s*/ism", '', $body);
}
$attachments = [];
if (preg_match_all("#\[url=([^\]]+?)\]\s*\[img=([^\[\]]*)\]([^\[\]]*)\[\/img\]\s*\[/url\]#ism", $body, $pictures, PREG_SET_ORDER)) {
@ -336,19 +349,51 @@ class Media
}
}
$url = PageInfo::getRelevantUrlFromBody($body);
if (!empty($url)) {
Logger::debug('Got page url', ['url' => $url]);
$attachments[$url] = ['uri-id' => $uriid, 'type' => self::UNKNOWN, 'url' => $url];
}
foreach ($attachments as $attachment) {
self::insert($attachment);
// Only store attachments that are part of the unshared body
if (strpos($unshared_body, $attachment['url']) !== false) {
self::insert($attachment);
}
}
return trim($body);
}
/**
* Add media links from a relevant url in the body
*
* @param integer $uriid
* @param string $body
*/
public static function insertFromRelevantUrl(int $uriid, string $body)
{
// Only remove the shared data from "real" reshares
$shared = BBCode::fetchShareAttributes($body);
if (!empty($shared['guid'])) {
// Don't look at the shared content
$body = preg_replace("/\s*\[share .*?\].*?\[\/share\]\s*/ism", '', $body);
}
// Remove all hashtags and mentions
$body = preg_replace("/([#@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '', $body);
// Search for pure links
if (preg_match_all("/\[url\](https?:.*?)\[\/url\]/ism", $body, $matches)) {
foreach ($matches[1] as $url) {
Logger::info('Got page url (link without description)', ['uri-id' => $uriid, 'url' => $url]);
self::insert(['uri-id' => $uriid, 'type' => self::UNKNOWN, 'url' => $url]);
}
}
// Search for links with descriptions
if (preg_match_all("/\[url\=(https?:.*?)\].*?\[\/url\]/ism", $body, $matches)) {
foreach ($matches[1] as $url) {
Logger::info('Got page url (link with description)', ['uri-id' => $uriid, 'url' => $url]);
self::insert(['uri-id' => $uriid, 'type' => self::UNKNOWN, 'url' => $url]);
}
}
}
/**
* Add media links from the attachment field
*
@ -357,6 +402,9 @@ class Media
*/
public static function insertFromAttachmentData(int $uriid, string $body)
{
// Don't look at the shared content
$body = preg_replace("/\s*\[share .*?\].*?\[\/share\]\s*/ism", '', $body);
$data = BBCode::getAttachmentData($body);
if (empty($data)) {
return;
@ -447,11 +495,12 @@ class Media
/**
* Split the attachment media in the three segments "visual", "link" and "additional"
*
* @param int $uri_id
* @param int $uri_id
* @param string $guid
* @param array $links ist of links that shouldn't be added
* @return array attachments
*/
public static function splitAttachments(int $uri_id, string $guid = '')
public static function splitAttachments(int $uri_id, string $guid = '', array $links = [])
{
$attachments = ['visual' => [], 'link' => [], 'additional' => []];
@ -462,8 +511,26 @@ class Media
$height = 0;
$selected = '';
$previews = [];
foreach ($media as $medium) {
foreach ($links as $link) {
if (Strings::compareLink($link, $medium['url'])) {
continue 2;
}
}
// Avoid adding separate media entries for previews
foreach ($previews as $preview) {
if (Strings::compareLink($preview, $medium['url'])) {
continue 2;
}
}
if (!empty($medium['preview'])) {
$previews[] = $medium['preview'];
}
$type = explode('/', current(explode(';', $medium['mimetype'])));
if (count($type) < 2) {
Logger::info('Unknown MimeType', ['type' => $type, 'media' => $medium]);
@ -511,4 +578,57 @@ class Media
}
return $attachments;
}
/**
* Add media attachments to the body
*
* @param int $uriid
* @param string $body
* @return string body
*/
public static function addAttachmentsToBody(int $uriid, string $body = '')
{
if (empty($body)) {
$item = Post::selectFirst(['body'], ['uri-id' => $uriid]);
if (!DBA::isResult($item)) {
return '';
}
$body = $item['body'];
}
$original_body = $body;
$body = preg_replace("/\s*\[attachment .*?\].*?\[\/attachment\]\s*/ism", '', $body);
foreach (self::getByURIId($uriid, [self::IMAGE, self::AUDIO, self::VIDEO]) as $media) {
if (Item::containsLink($body, $media['url'])) {
continue;
}
if ($media['type'] == self::IMAGE) {
if (!empty($media['preview'])) {
if (!empty($media['description'])) {
$body .= "\n[url=" . $media['url'] . "][img=" . $media['preview'] . ']' . $media['description'] .'[/img][/url]';
} else {
$body .= "\n[url=" . $media['url'] . "][img]" . $media['preview'] .'[/img][/url]';
}
} else {
if (!empty($media['description'])) {
$body .= "\n[img=" . $media['url'] . ']' . $media['description'] .'[/img]';
} else {
$body .= "\n[img]" . $media['url'] .'[/img]';
}
}
} elseif ($media['type'] == self::AUDIO) {
$body .= "\n[audio]" . $media['url'] . "[/audio]\n";
} elseif ($media['type'] == self::VIDEO) {
$body .= "\n[video]" . $media['url'] . "[/video]\n";
}
}
if (preg_match("/.*(\[attachment.*?\].*?\[\/attachment\]).*/ism", $original_body, $match)) {
$body .= "\n" . $match[1];
}
return $body;
}
}

View File

@ -412,7 +412,12 @@ class Profile
}
}
$p = [];
// Expected profile/vcard.tpl profile.* template variables
$p = [
'address' => null,
'edit' => null,
'upubkey' => null,
];
foreach ($profile as $k => $v) {
$k = str_replace('-', '_', $k);
$p[$k] = $v;

View File

@ -56,7 +56,7 @@ class Queue extends BaseAdmin
}
// @TODO Move to Model\WorkerQueue::getEntries()
$entries = DBA::select('workerqueue', ['id', 'parameter', 'created', 'priority'], $condition, ['limit' => 999, 'order' => ['created']]);
$entries = DBA::select('workerqueue', ['id', 'parameter', 'created', 'priority', 'command'], $condition, ['limit' => 999, 'order' => ['created']]);
$r = [];
while ($entry = DBA::fetch($entries)) {
@ -73,6 +73,7 @@ class Queue extends BaseAdmin
'$page' => $sub_title,
'$count' => count($r),
'$id_header' => DI::l10n()->t('ID'),
'$command_header' => DI::l10n()->t('Command'),
'$param_header' => DI::l10n()->t('Job Parameters'),
'$created_header' => DI::l10n()->t('Created'),
'$prio_header' => DI::l10n()->t('Priority'),

View File

@ -38,7 +38,7 @@ class Accounts extends BaseApi
public static function rawContent(array $parameters = [])
{
if (empty($parameters['id'])) {
DI::mstdnError()->RecordNotFound();
DI::mstdnError()->UnprocessableEntity();
}
$id = $parameters['id'];
@ -46,7 +46,7 @@ class Accounts extends BaseApi
DI::mstdnError()->RecordNotFound();
}
$account = DI::mstdnAccount()->createFromContactId($id);
$account = DI::mstdnAccount()->createFromContactId($id, self::getCurrentUserID());
System::jsonExit($account);
}
}

View File

@ -0,0 +1,90 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon\Accounts;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/methods/accounts/
*/
class Followers extends BaseApi
{
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
self::login();
$uid = self::getCurrentUserID();
if (empty($parameters['id'])) {
DI::mstdnError()->UnprocessableEntity();
}
$id = $parameters['id'];
if (!DBA::exists('contact', ['id' => $id, 'uid' => 0])) {
DI::mstdnError()->RecordNotFound();
}
// Return results older than this id
$max_id = (int)!isset($_REQUEST['max_id']) ? 0 : $_REQUEST['max_id'];
// Return results newer than this id
$since_id = (int)!isset($_REQUEST['since_id']) ? 0 : $_REQUEST['since_id'];
// Maximum number of results to return. Defaults to 20.
$limit = (int)!isset($_REQUEST['limit']) ? 20 : $_REQUEST['limit'];
$params = ['order' => ['cid' => true], 'limit' => $limit];
$condition = ['relation-cid' => $id, 'follows' => true];
if (!empty($max_id)) {
$condition = DBA::mergeConditions($condition, ["`cid` < ?", $max_id]);
}
if (!empty($since_id)) {
$condition = DBA::mergeConditions($condition, ["`cid` > ?", $since_id]);
}
if (!empty($min_id)) {
$condition = DBA::mergeConditions($condition, ["`cid` > ?", $min_id]);
$params['order'] = ['cid'];
}
$followers = DBA::select('contact-relation', ['cid'], $condition, $parameters);
while ($follower = DBA::fetch($followers)) {
$accounts[] = DI::mstdnAccount()->createFromContactId($follower['cid'], $uid);
}
DBA::close($followers);
if (!empty($min_id)) {
array_reverse($accounts);
}
System::jsonExit($accounts);
}
}

View File

@ -0,0 +1,90 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon\Accounts;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/methods/accounts/
*/
class Following extends BaseApi
{
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
self::login();
$uid = self::getCurrentUserID();
if (empty($parameters['id'])) {
DI::mstdnError()->UnprocessableEntity();
}
$id = $parameters['id'];
if (!DBA::exists('contact', ['id' => $id, 'uid' => 0])) {
DI::mstdnError()->RecordNotFound();
}
// Return results older than this id
$max_id = (int)!isset($_REQUEST['max_id']) ? 0 : $_REQUEST['max_id'];
// Return results newer than this id
$since_id = (int)!isset($_REQUEST['since_id']) ? 0 : $_REQUEST['since_id'];
// Maximum number of results to return. Defaults to 20.
$limit = (int)!isset($_REQUEST['limit']) ? 20 : $_REQUEST['limit'];
$params = ['order' => ['relation-cid' => true], 'limit' => $limit];
$condition = ['cid' => $id, 'follows' => true];
if (!empty($max_id)) {
$condition = DBA::mergeConditions($condition, ["`relation-cid` < ?", $max_id]);
}
if (!empty($since_id)) {
$condition = DBA::mergeConditions($condition, ["`relation-cid` > ?", $since_id]);
}
if (!empty($min_id)) {
$condition = DBA::mergeConditions($condition, ["`relation-cid` > ?", $min_id]);
$params['order'] = ['cid'];
}
$followers = DBA::select('contact-relation', ['relation-cid'], $condition, $parameters);
while ($follower = DBA::fetch($followers)) {
$accounts[] = DI::mstdnAccount()->createFromContactId($follower['relation-cid'], $uid);
}
DBA::close($followers);
if (!empty($min_id)) {
array_reverse($accounts);
}
System::jsonExit($accounts);
}
}

View File

@ -0,0 +1,42 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon\Accounts;
use Friendica\Core\System;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/methods/accounts/
*/
class IdentityProofs extends BaseApi
{
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
self::login();
System::jsonExit([]);
}
}

View File

@ -0,0 +1,66 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon\Accounts;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/methods/accounts/
*/
class Lists extends BaseApi
{
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
self::login();
$uid = self::getCurrentUserID();
if (empty($parameters['id'])) {
DI::mstdnError()->UnprocessableEntity();
}
$id = $parameters['id'];
if (!DBA::exists('contact', ['id' => $id, 'uid' => 0])) {
DI::mstdnError()->RecordNotFound();
}
$lists = [];
$cdata = Contact::getPublicAndUserContacID($id, $uid);
if (!empty($cdata['user'])) {
$groups = DBA::select('group_member', ['gid'], ['contact-id' => $cdata['user']]);
while ($group = DBA::fetch($groups)) {
$lists[] = DI::mstdnList()->createFromGroupId($group['gid']);
}
DBA::close($groups);
}
System::jsonExit($lists);
}
}

View File

@ -0,0 +1,101 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon\Accounts;
use Friendica\Core\Search as CoreSearch;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Module\BaseApi;
use Friendica\Object\Search\ContactResult;
/**
* @see https://docs.joinmastodon.org/methods/accounts/
*/
class Search extends BaseApi
{
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
self::login();
$uid = self::getCurrentUserID();
// What to search for
$q = (int)!isset($_REQUEST['q']) ? 0 : $_REQUEST['q'];
// Maximum number of results. Defaults to 40.
$limit = (int)!isset($_REQUEST['limit']) ? 40 : $_REQUEST['limit'];
// Attempt WebFinger lookup. Defaults to false. Use this when q is an exact address.
$resolve = (int)!isset($_REQUEST['resolve']) ? 0 : $_REQUEST['resolve'];
// Only who the user is following. Defaults to false.
$following = (int)!isset($_REQUEST['following']) ? 0 : $_REQUEST['following'];
$accounts = [];
if (!$following) {
if ((strrpos($q, '@') > 0) && $resolve) {
$results = CoreSearch::getContactsFromProbe($q);
}
if (empty($results)) {
if (DI::config()->get('system', 'poco_local_search')) {
$results = CoreSearch::getContactsFromLocalDirectory($q, CoreSearch::TYPE_ALL, 0, $limit);
} elseif (!empty(DI::config()->get('system', 'directory'))) {
$results = CoreSearch::getContactsFromGlobalDirectory($q, CoreSearch::TYPE_ALL, 1);
}
}
if (!empty($results)) {
$counter = 0;
foreach ($results->getResults() as $result) {
if (++$counter > $limit) {
continue;
}
if ($result instanceof ContactResult) {
$id = Contact::getIdForURL($result->getUrl(), 0, false);
$accounts[] = DI::mstdnAccount()->createFromContactId($id, $uid);
}
}
}
} else {
$contacts = Contact::searchByName($q, '', $uid);
$counter = 0;
foreach ($contacts as $contact) {
if (!in_array($contact['rel'], [Contact::SHARING, Contact::FRIEND])) {
continue;
}
if (++$counter > $limit) {
continue;
}
$accounts[] = DI::mstdnAccount()->createFromContactId($contact['id'], $uid);
}
DBA::close($contacts);
}
System::jsonExit($accounts);
}
}

View File

@ -43,7 +43,7 @@ class Statuses extends BaseApi
public static function rawContent(array $parameters = [])
{
if (empty($parameters['id'])) {
DI::mstdnError()->RecordNotFound();
DI::mstdnError()->UnprocessableEntity();
}
$id = $parameters['id'];
@ -52,7 +52,7 @@ class Statuses extends BaseApi
}
// Show only statuses with media attached? Defaults to false.
$only_media = (bool)!isset($_REQUEST['only_media']) ? false : ($_REQUEST['only_media'] == 'true'); // Currently not supported
$only_media = (bool)!isset($_REQUEST['only_media']) ? false : ($_REQUEST['only_media'] == 'true');
// Return results older than this id
$max_id = (int)!isset($_REQUEST['max_id']) ? 0 : $_REQUEST['max_id'];
// Return results newer than this id
@ -64,12 +64,23 @@ class Statuses extends BaseApi
$params = ['order' => ['uri-id' => true], 'limit' => $limit];
$condition = ['author-id' => $id, 'private' => [Item::PUBLIC, Item::UNLISTED],
'uid' => 0, 'network' => Protocol::FEDERATED];
$uid = self::getCurrentUserID();
if (!$uid) {
$condition = ['author-id' => $id, 'private' => [Item::PUBLIC, Item::UNLISTED],
'uid' => 0, 'network' => Protocol::FEDERATED];
} else {
$condition = ["`author-id` = ? AND (`uid` = 0 OR (`uid` = ? AND NOT `global`))", $id, $uid];
}
$condition = DBA::mergeConditions($condition, ["(`gravity` IN (?, ?) OR (`gravity` = ? AND `vid` = ?))",
GRAVITY_PARENT, GRAVITY_COMMENT, GRAVITY_ACTIVITY, Verb::getID(Activity::ANNOUNCE)]);
if ($only_media) {
$condition = DBA::mergeConditions($condition, ["`uri-id` IN (SELECT `uri-id` FROM `post-media` WHERE `type` IN (?, ?, ?))",
Post\Media::AUDIO, Post\Media::IMAGE, Post\Media::VIDEO]);
}
if (!empty($max_id)) {
$condition = DBA::mergeConditions($condition, ["`uri-id` < ?", $max_id]);
}
@ -83,11 +94,11 @@ class Statuses extends BaseApi
$params['order'] = ['uri-id'];
}
$items = Post::selectForUser(0, ['uri-id', 'uid'], $condition, $params);
$items = Post::selectForUser($uid, ['uri-id'], $condition, $params);
$statuses = [];
while ($item = Post::fetch($items)) {
$statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $item['uid']);
$statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid);
}
DBA::close($items);

View File

@ -0,0 +1,58 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon\Accounts;
use Friendica\Core\System;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\User;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/methods/accounts/
*/
class VerifyCredentials extends BaseApi
{
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
self::login();
$uid = self::getCurrentUserID();
$self = User::getOwnerDataById($uid);
if (empty($self)) {
DI::mstdnError()->InternalError();
}
$cdata = Contact::getPublicAndUserContacID($self['id'], $uid);
if (empty($cdata)) {
DI::mstdnError()->InternalError();
}
// @todo Support the source property,
$account = DI::mstdnAccount()->createFromContactId($cdata['user'], $uid);
System::jsonExit($account);
}
}

View File

@ -0,0 +1,43 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon;
use Friendica\Core\System;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/methods/announcements/
*/
class Announcements extends BaseApi
{
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
self::login();
// @todo Possibly use the message from the pageheader addon for this
System::jsonExit([]);
}
}

View File

@ -0,0 +1,84 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Module\BaseApi;
use Friendica\Util\Network;
/**
* Apps class to register new OAuth clients
*/
class Apps extends BaseApi
{
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function post(array $parameters = [])
{
// Workaround for AndStatus, see issue https://github.com/andstatus/andstatus/issues/538
if (empty($_REQUEST['client_name']) || empty($_REQUEST['redirect_uris'])) {
$postdata = Network::postdata();
if (!empty($postdata)) {
$_REQUEST = json_decode($postdata, true);
if (empty($_REQUEST)) {
DI::mstdnError()->UnprocessableEntity(DI::l10n()->t('Missing parameters'));
}
}
}
$name = $_REQUEST['client_name'] ?? '';
$redirect = $_REQUEST['redirect_uris'] ?? '';
$scopes = $_REQUEST['scopes'] ?? 'read';
$website = $_REQUEST['website'] ?? '';
if (empty($name) || empty($redirect)) {
DI::mstdnError()->UnprocessableEntity(DI::l10n()->t('Missing parameters'));
}
$client_id = bin2hex(random_bytes(32));
$client_secret = bin2hex(random_bytes(32));
$fields = ['client_id' => $client_id, 'client_secret' => $client_secret, 'name' => $name, 'redirect_uri' => $redirect];
if (!empty($scopes)) {
$fields['scopes'] = $scopes;
}
$fields['read'] = (stripos($scopes, 'read') !== false);
$fields['write'] = (stripos($scopes, 'write') !== false);
$fields['follow'] = (stripos($scopes, 'follow') !== false);
if (!empty($website)) {
$fields['website'] = $website;
}
if (!DBA::insert('application', $fields)) {
DI::mstdnError()->InternalError();
}
System::jsonExit(DI::mstdnApplication()->createFromApplicationId(DBA::lastInsertId()));
}
}

View File

@ -0,0 +1,89 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/methods/accounts/blocks/
*/
class Blocks extends BaseApi
{
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
self::login();
$uid = self::getCurrentUserID();
if (empty($parameters['id'])) {
DI::mstdnError()->UnprocessableEntity();
}
$id = $parameters['id'];
if (!DBA::exists('contact', ['id' => $id, 'uid' => 0])) {
DI::mstdnError()->RecordNotFound();
}
// Return results older than this id
$max_id = (int)!isset($_REQUEST['max_id']) ? 0 : $_REQUEST['max_id'];
// Return results newer than this id
$since_id = (int)!isset($_REQUEST['since_id']) ? 0 : $_REQUEST['since_id'];
// Maximum number of results. Defaults to 40.
$limit = (int)!isset($_REQUEST['limit']) ? 40 : $_REQUEST['limit'];
$params = ['order' => ['cid' => true], 'limit' => $limit];
$condition = ['cid' => $id, 'blocked' => true, 'uid' => $uid];
if (!empty($max_id)) {
$condition = DBA::mergeConditions($condition, ["`cid` < ?", $max_id]);
}
if (!empty($since_id)) {
$condition = DBA::mergeConditions($condition, ["`cid` > ?", $since_id]);
}
if (!empty($min_id)) {
$condition = DBA::mergeConditions($condition, ["`cid` > ?", $min_id]);
$params['order'] = ['cid'];
}
$followers = DBA::select('user-contact', ['cid'], $condition, $parameters);
while ($follower = DBA::fetch($followers)) {
$accounts[] = DI::mstdnAccount()->createFromContactId($follower['cid'], $uid);
}
DBA::close($followers);
if (!empty($min_id)) {
array_reverse($accounts);
}
System::jsonExit($accounts);
}
}

View File

@ -0,0 +1,86 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Post;
use Friendica\Module\BaseApi;
use Friendica\Network\HTTPException;
/**
* @see https://docs.joinmastodon.org/methods/accounts/bookmarks/
*/
class Bookmarks extends BaseApi
{
/**
* @param array $parameters
* @throws HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
self::login();
$uid = self::getCurrentUserID();
// Maximum number of results to return. Defaults to 20.
$limit = (int)!isset($_REQUEST['limit']) ? 20 : $_REQUEST['limit'];
// Return results older than id
$max_id = (int)!isset($_REQUEST['max_id']) ? 0 : $_REQUEST['max_id'];
// Return results newer than id
$since_id = (int)!isset($_REQUEST['since_id']) ? 0 : $_REQUEST['since_id'];
// Return results immediately newer than id
$min_id = (int)!isset($_REQUEST['min_id']) ? 0 : $_REQUEST['min_id'];
$params = ['order' => ['uri-id' => true], 'limit' => $limit];
$condition = ['pinned' => true, 'uid' => $uid];
if (!empty($max_id)) {
$condition = DBA::mergeConditions($condition, ["`uri-id` < ?", $max_id]);
}
if (!empty($since_id)) {
$condition = DBA::mergeConditions($condition, ["`uri-id` > ?", $since_id]);
}
if (!empty($min_id)) {
$condition = DBA::mergeConditions($condition, ["`uri-id` > ?", $min_id]);
$params['order'] = ['uri-id'];
}
$items = Post::selectThreadForUser($uid, ['uri-id'], $condition, $params);
$statuses = [];
while ($item = Post::fetch($items)) {
$statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid);
}
DBA::close($items);
if (!empty($min_id)) {
array_reverse($statuses);
}
System::jsonExit($statuses);
}
}

View File

@ -44,7 +44,7 @@ class Directory extends BaseApi
{
$offset = (int)!isset($_REQUEST['offset']) ? 0 : $_REQUEST['offset'];
$limit = (int)!isset($_REQUEST['limit']) ? 40 : $_REQUEST['limit'];
$order = !isset($_REQUEST['order']) ? 'active' : $_REQUEST['order'];
$order = $_REQUEST['order'] ?? 'active';
$local = (bool)!isset($_REQUEST['local']) ? false : ($_REQUEST['local'] == 'true');
Logger::info('directory', ['offset' => $offset, 'limit' => $limit, 'order' => $order, 'local' => $local]);

View File

@ -0,0 +1,40 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon;
use Friendica\Core\System;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/methods/accounts/endorsements/
*/
class Endorsements extends BaseApi
{
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
System::jsonExit([]);
}
}

View File

@ -0,0 +1,81 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Post;
use Friendica\Module\BaseApi;
use Friendica\Network\HTTPException;
use Friendica\Protocol\Activity;
/**
* @see https://docs.joinmastodon.org/methods/accounts/favourites/
*/
class Favourited extends BaseApi
{
/**
* @param array $parameters
* @throws HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
self::login();
$uid = self::getCurrentUserID();
// Maximum number of results to return. Defaults to 20.
$limit = (int)!isset($_REQUEST['limit']) ? 20 : $_REQUEST['limit'];
// Return results immediately newer than id
$min_id = (int)!isset($_REQUEST['min_id']) ? 0 : $_REQUEST['min_id'];
// Return results older than id
$max_id = (int)!isset($_REQUEST['max_id']) ? 0 : $_REQUEST['max_id'];
$params = ['order' => ['thr-parent-id' => true], 'limit' => $limit];
$condition = ['gravity' => GRAVITY_ACTIVITY, 'verb' => Activity::LIKE, 'uid' => $uid];
if (!empty($max_id)) {
$condition = DBA::mergeConditions($condition, ["`thr-parent-id` < ?", $max_id]);
}
if (!empty($min_id)) {
$condition = DBA::mergeConditions($condition, ["`thr-parent-id` > ?", $min_id]);
$params['order'] = ['thr-parent-id'];
}
$items = Post::selectForUser($uid, ['thr-parent-id'], $condition, $params);
$statuses = [];
while ($item = Post::fetch($items)) {
$statuses[] = DI::mstdnStatus()->createFromUriId($item['thr-parent-id'], $uid);
}
DBA::close($items);
if (!empty($min_id)) {
array_reverse($statuses);
}
System::jsonExit($statuses);
}
}

View File

@ -0,0 +1,115 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon;
use Friendica\Core\System;
use Friendica\DI;
use Friendica\Module\BaseApi;
use Friendica\Model\Group;
/**
* @see https://docs.joinmastodon.org/methods/timelines/lists/
*/
class Lists extends BaseApi
{
public static function delete(array $parameters = [])
{
self::login();
$uid = self::getCurrentUserID();
if (empty($parameters['id'])) {
DI::mstdnError()->UnprocessableEntity();
}
if (!Group::exists($parameters['id'], $uid)) {
DI::mstdnError()->RecordNotFound();
}
if (!Group::remove($parameters['id'])) {
DI::mstdnError()->InternalError();
}
System::jsonExit([]);
}
public static function post(array $parameters = [])
{
self::login();
$uid = self::getCurrentUserID();
$title = $_REQUEST['title'] ?? '';
if (empty($title)) {
DI::mstdnError()->UnprocessableEntity();
}
Group::create($uid, $title);
$id = Group::getIdByName($uid, $title);
if (!$id) {
DI::mstdnError()->InternalError();
}
System::jsonExit(DI::mstdnList()->createFromGroupId($id));
}
public static function put(array $parameters = [])
{
$data = self::getPutData();
if (empty($data['title']) || empty($parameters['id'])) {
DI::mstdnError()->UnprocessableEntity();
}
Group::update($parameters['id'], $data['title']);
}
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
self::login();
$uid = self::getCurrentUserID();
if (empty($parameters['id'])) {
$lists = [];
$groups = Group::getByUserId($uid);
foreach ($groups as $group) {
$lists[] = DI::mstdnList()->createFromGroupId($group['id']);
}
} else {
$id = $parameters['id'];
if (!Group::exists($id, $uid)) {
DI::mstdnError()->RecordNotFound();
}
$lists = DI::mstdnList()->createFromGroupId($id);
}
System::jsonExit($lists);
}
}

View File

@ -0,0 +1,110 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon\Lists;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/methods/timelines/lists/
*
* Currently the output will be unordered since we use public contact ids in the api and not user contact ids.
*/
class Accounts extends BaseApi
{
public static function delete(array $parameters = [])
{
self::unsupported('delete');
}
public static function post(array $parameters = [])
{
self::unsupported('post');
}
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
self::login();
$uid = self::getCurrentUserID();
if (empty($parameters['id'])) {
DI::mstdnError()->UnprocessableEntity();
}
$id = $parameters['id'];
if (!DBA::exists('group', ['id' => $id, 'uid' => $uid])) {
DI::mstdnError()->RecordNotFound();
}
// Return results older than this id
$max_id = (int)!isset($_REQUEST['max_id']) ? 0 : $_REQUEST['max_id'];
// Return results newer than this id
$since_id = (int)!isset($_REQUEST['since_id']) ? 0 : $_REQUEST['since_id'];
// Maximum number of results. Defaults to 40. Max 40.
// Set to 0 in order to get all accounts without pagination.
$limit = (int)!isset($_REQUEST['limit']) ? 40 : $_REQUEST['limit'];
$params = ['order' => ['contact-id' => true]];
if ($limit != 0) {
$params['limit'] = $limit;
}
$condition = ['gid' => $id];
if (!empty($max_id)) {
$condition = DBA::mergeConditions($condition, ["`contact-id` < ?", $max_id]);
}
if (!empty($since_id)) {
$condition = DBA::mergeConditions($condition, ["`contact-id` > ?", $since_id]);
}
if (!empty($min_id)) {
$condition = DBA::mergeConditions($condition, ["`contact-id` > ?", $min_id]);
$params['order'] = ['contact-id'];
}
$accounts = [];
$members = DBA::select('group_member', ['contact-id'], $condition, $params);
while ($member = DBA::fetch($members)) {
$accounts[] = DI::mstdnAccount()->createFromContactId($member['contact-id'], $uid);
}
DBA::close($members);
if (!empty($min_id)) {
array_reverse($accounts);
}
System::jsonExit($accounts);
}
}

View File

@ -0,0 +1,47 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon;
use Friendica\Core\System;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/methods/timelines/markers/
*/
class Markers extends BaseApi
{
public static function post(array $parameters = [])
{
self::unsupported('post');
}
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
self::login();
System::jsonExit([]);
}
}

View File

@ -0,0 +1,60 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon;
use Friendica\Core\System;
use Friendica\DI;
use Friendica\Model\Photo;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/methods/statuses/media/
*/
class Media extends BaseApi
{
public static function put(array $parameters = [])
{
$data = self::getPutData();
self::unsupported('put');
}
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
self::login();
$uid = self::getCurrentUserID();
if (empty($parameters['id'])) {
DI::mstdnError()->UnprocessableEntity();
}
$id = $parameters['id'];
if (!Photo::exists(['id' => $id, 'uid' => $uid])) {
DI::mstdnError()->RecordNotFound();
}
System::jsonExit(DI::mstdnAttachment()->createFromPhoto($id));
}
}

View File

@ -0,0 +1,89 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/methods/accounts/mutes/
*/
class Mutes extends BaseApi
{
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
self::login();
$uid = self::getCurrentUserID();
if (empty($parameters['id'])) {
DI::mstdnError()->UnprocessableEntity();
}
$id = $parameters['id'];
if (!DBA::exists('contact', ['id' => $id, 'uid' => 0])) {
DI::mstdnError()->RecordNotFound();
}
// Return results older than this id
$max_id = (int)!isset($_REQUEST['max_id']) ? 0 : $_REQUEST['max_id'];
// Return results newer than this id
$since_id = (int)!isset($_REQUEST['since_id']) ? 0 : $_REQUEST['since_id'];
// Maximum number of results. Defaults to 40.
$limit = (int)!isset($_REQUEST['limit']) ? 40 : $_REQUEST['limit'];
$params = ['order' => ['cid' => true], 'limit' => $limit];
$condition = ['cid' => $id, 'ignored' => true, 'uid' => $uid];
if (!empty($max_id)) {
$condition = DBA::mergeConditions($condition, ["`cid` < ?", $max_id]);
}
if (!empty($since_id)) {
$condition = DBA::mergeConditions($condition, ["`cid` > ?", $since_id]);
}
if (!empty($min_id)) {
$condition = DBA::mergeConditions($condition, ["`cid` > ?", $min_id]);
$params['order'] = ['cid'];
}
$followers = DBA::select('user-contact', ['cid'], $condition, $parameters);
while ($follower = DBA::fetch($followers)) {
$accounts[] = DI::mstdnAccount()->createFromContactId($follower['cid'], $uid);
}
DBA::close($followers);
if (!empty($min_id)) {
array_reverse($accounts);
}
System::jsonExit($accounts);
}
}

View File

@ -0,0 +1,122 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Notification;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/methods/accounts/mutes/
*/
class Notifications extends BaseApi
{
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
self::login();
$uid = self::getCurrentUserID();
if (!empty($parameters['id'])) {
$id = $parameters['id'];
if (!DBA::exists('notify', ['id' => $id, 'uid' => $uid])) {
DI::mstdnError()->RecordNotFound();
}
System::jsonExit(DI::mstdnNotification()->createFromNotifyId($id));
}
// Return results older than this ID
$max_id = (int)!isset($_REQUEST['max_id']) ? 0 : $_REQUEST['max_id'];
// Return results newer than this ID
$since_id = (int)!isset($_REQUEST['since_id']) ? 0 : $_REQUEST['since_id'];
// Return results immediately newer than this ID
$min_id = (int)!isset($_REQUEST['min_id']) ? 0 : $_REQUEST['min_id'];
// Maximum number of results to return (default 20)
$limit = (int)!isset($_REQUEST['limit']) ? 20 : $_REQUEST['limit'];
// Array of types to exclude (follow, favourite, reblog, mention, poll, follow_request)
$exclude_types = $_REQUEST['exclude_types'] ?? [];
// Return only notifications received from this account
$account_id = (int)!isset($_REQUEST['account_id']) ? 0 : $_REQUEST['account_id'];
$params = ['order' => ['id' => true], 'limit' => $limit];
$condition = ['uid' => $uid, 'seen' => false, 'type' => []];
if (!empty($account_id)) {
$contact = Contact::getById($account_id, ['url']);
if (!empty($contact['url'])) {
$condition['url'] = $contact['url'];
}
}
if (!in_array('follow_request', $exclude_types)) {
$condition['type'] = array_merge($condition['type'], [Notification\Type::INTRO]);
}
if (!in_array('mention', $exclude_types)) {
$condition['type'] = array_merge($condition['type'],
[Notification\Type::WALL, Notification\Type::COMMENT, Notification\Type::MAIL, Notification\Type::TAG_SELF, Notification\Type::POKE]);
}
if (!in_array('status', $exclude_types)) {
$condition['type'] = array_merge($condition['type'], [Notification\Type::SHARE]);
}
if (!empty($max_id)) {
$condition = DBA::mergeConditions($condition, ["`id` < ?", $max_id]);
}
if (!empty($since_id)) {
$condition = DBA::mergeConditions($condition, ["`id` > ?", $since_id]);
}
if (!empty($min_id)) {
$condition = DBA::mergeConditions($condition, ["`id` > ?", $min_id]);
$params['order'] = ['id'];
}
$notifications = [];
$notify = DBA::select('notify', ['id'], $condition, $params);
while ($notification = DBA::fetch($notify)) {
$notifications[] = DI::mstdnNotification()->createFromNotifyId($notification['id']);
}
if (!empty($min_id)) {
array_reverse($notifications);
}
System::jsonExit($notifications);
}
}

View File

@ -0,0 +1,61 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon;
use Friendica\Core\System;
use Friendica\DI;
use Friendica\Model\User;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/methods/accounts/preferences/
*/
class Preferences extends BaseApi
{
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
self::login();
$uid = self::getCurrentUserID();
$user = User::getById($uid, ['language', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid']);
if (!empty($user['allow_cid']) || !empty($user['allow_gid']) || !empty($user['deny_cid']) || !empty($user['deny_gid'])) {
$visibility = 'private';
} elseif (DI::pConfig()->get($uid, 'system', 'unlisted')) {
$visibility = 'unlisted';
} else {
$visibility = 'public';
}
$sensitive = false;
$language = $user['language'];
$media = DI::pConfig()->get($uid, 'nsfw', 'disable') ? 'show_all' : 'default';
$spoilers = DI::pConfig()->get($uid, 'system', 'disable_cw');
$preferences = new \Friendica\Object\Api\Mastodon\Preferences($visibility, $sensitive, $language, $media, $spoilers);
System::jsonExit($preferences);
}
}

View File

@ -0,0 +1,40 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon;
use Friendica\Core\System;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/methods/proofs/
*/
class Proofs extends BaseApi
{
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
System::jsonError(404, ['error' => 'Record not found']);
}
}

View File

@ -0,0 +1,40 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon;
use Friendica\Core\System;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/methods/statuses/scheduled_statuses/
*/
class ScheduledStatuses extends BaseApi
{
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
System::jsonExit([]);
}
}

View File

@ -0,0 +1,56 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon;
use Friendica\Core\System;
use Friendica\DI;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/methods/statuses/
*/
class Statuses extends BaseApi
{
public static function post(array $parameters = [])
{
$data = self::getJsonPostData();
self::unsupported('post');
}
public static function delete(array $parameters = [])
{
self::unsupported('delete');
}
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
if (empty($parameters['id'])) {
DI::mstdnError()->UnprocessableEntity();
}
System::jsonExit(DI::mstdnStatus()->createFromUriId($parameters['id'], self::getCurrentUserID()));
}
}

View File

@ -0,0 +1,115 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon\Statuses;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Post;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/methods/statuses/
*/
class Context extends BaseApi
{
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
$uid = self::getCurrentUserID();
if (empty($parameters['id'])) {
DI::mstdnError()->UnprocessableEntity();
}
$id = $parameters['id'];
$parent = Post::selectFirst(['parent-uri-id'], ['uri-id' => $id]);
if (!DBA::isResult($parent)) {
DI::mstdnError()->RecordNotFound();
}
$parents = [];
$children = [];
$posts = Post::select(['uri-id', 'thr-parent-id'],
['parent-uri-id' => $parent['parent-uri-id'], 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT]], [], false);
while ($post = Post::fetch($posts)) {
if ($post['uri-id'] == $post['thr-parent-id']) {
continue;
}
$parents[$post['uri-id']] = $post['thr-parent-id'];
$children[$post['thr-parent-id']][] = $post['uri-id'];
}
DBA::close($posts);
$statuses = ['ancestors' => [], 'descendants' => []];
$ancestors = [];
foreach (self::getParents($id, $parents) as $ancestor) {
$ancestors[$ancestor] = DI::mstdnStatus()->createFromUriId($ancestor, $uid);
}
ksort($ancestors);
foreach ($ancestors as $ancestor) {
$statuses['ancestors'][] = $ancestor;
}
$descendants = [];
foreach (self::getChildren($id, $children) as $descendant) {
$descendants[] = DI::mstdnStatus()->createFromUriId($descendant, $uid);
}
ksort($descendants);
foreach ($descendants as $descendant) {
$statuses['descendants'][] = $descendant;
}
System::jsonExit($statuses);
}
private static function getParents(int $id, array $parents, array $list = [])
{
if (!empty($parents[$id])) {
$list[] = $parents[$id];
$list = self::getParents($parents[$id], $parents, $list);
}
return $list;
}
private static function getChildren(int $id, array $children, array $list = [])
{
if (!empty($children[$id])) {
foreach ($children[$id] as $child) {
$list[] = $child;
$list = self::getChildren($child, $children, $list);
}
}
return $list;
}
}

View File

@ -0,0 +1,62 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon\Statuses;
use Friendica\Core\System;
use Friendica\DI;
use Friendica\Model\Post;
use Friendica\Module\BaseApi;
use Friendica\Protocol\Activity;
/**
* @see https://docs.joinmastodon.org/methods/statuses/
*/
class FavouritedBy extends BaseApi
{
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
$uid = self::getCurrentUserID();
if (empty($parameters['id'])) {
DI::mstdnError()->UnprocessableEntity();
}
$id = $parameters['id'];
if (!Post::exists(['uri-id' => $id, 'uid' => [0, $uid]])) {
DI::mstdnError()->RecordNotFound();
}
$activities = Post::select(['author-id'], ['thr-parent-id' => $id, 'gravity' => GRAVITY_ACTIVITY, 'verb' => Activity::LIKE], [], false);
$accounts = [];
while ($activity = Post::fetch($activities)) {
$accounts[] = DI::mstdnAccount()->createFromContactId($activity['author-id'], $uid);
}
System::jsonExit($accounts);
}
}

View File

@ -0,0 +1,62 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon\Statuses;
use Friendica\Core\System;
use Friendica\DI;
use Friendica\Model\Post;
use Friendica\Module\BaseApi;
use Friendica\Protocol\Activity;
/**
* @see https://docs.joinmastodon.org/methods/statuses/
*/
class RebloggedBy extends BaseApi
{
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
$uid = self::getCurrentUserID();
if (empty($parameters['id'])) {
DI::mstdnError()->UnprocessableEntity();
}
$id = $parameters['id'];
if (!Post::exists(['uri-id' => $id, 'uid' => [0, $uid]])) {
DI::mstdnError()->RecordNotFound();
}
$activities = Post::select(['author-id'], ['thr-parent-id' => $id, 'gravity' => GRAVITY_ACTIVITY, 'verb' => Activity::ANNOUNCE], [], false);
$accounts = [];
while ($activity = Post::fetch($activities)) {
$accounts[] = DI::mstdnAccount()->createFromContactId($activity['author-id'], $uid);
}
System::jsonExit($accounts);
}
}

View File

@ -0,0 +1,56 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon;
use Friendica\Core\System;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/methods/accounts/suggestions/
*/
class Suggestions extends BaseApi
{
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
self::login();
$uid = self::getCurrentUserID();
// Maximum number of results to return. Defaults to 40.
$limit = (int)!isset($_REQUEST['limit']) ? 40 : $_REQUEST['limit'];
$suggestions = Contact\Relation::getSuggestions($uid, 0, $limit);
$accounts = [];
foreach ($suggestions as $suggestion) {
$accounts[] = DI::mstdnAccount()->createFromContactId($suggestion['id'], $uid);
}
System::jsonExit($accounts);
}
}

View File

@ -0,0 +1,92 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon\Timelines;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Post;
use Friendica\Module\BaseApi;
use Friendica\Network\HTTPException;
/**
* @see https://docs.joinmastodon.org/methods/timelines/
*/
class Home extends BaseApi
{
/**
* @param array $parameters
* @throws HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
self::login();
$uid = self::getCurrentUserID();
// Return results older than id
$max_id = (int)!isset($_REQUEST['max_id']) ? 0 : $_REQUEST['max_id'];
// Return results newer than id
$since_id = (int)!isset($_REQUEST['since_id']) ? 0 : $_REQUEST['since_id'];
// Return results immediately newer than id
$min_id = (int)!isset($_REQUEST['min_id']) ? 0 : $_REQUEST['min_id'];
// Maximum number of results to return. Defaults to 20.
$limit = (int)!isset($_REQUEST['limit']) ? 20 : $_REQUEST['limit'];
// Return only local statuses? Defaults to false.
$local = (bool)!isset($_REQUEST['local']) ? false : ($_REQUEST['local'] == 'true');
$params = ['order' => ['uri-id' => true], 'limit' => $limit];
$condition = ['gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT], 'uid' => $uid];
if ($local) {
$condition = DBA::mergeConditions($condition, ["`uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `origin`)"]);
}
if (!empty($max_id)) {
$condition = DBA::mergeConditions($condition, ["`uri-id` < ?", $max_id]);
}
if (!empty($since_id)) {
$condition = DBA::mergeConditions($condition, ["`uri-id` > ?", $since_id]);
}
if (!empty($min_id)) {
$condition = DBA::mergeConditions($condition, ["`uri-id` > ?", $min_id]);
$params['order'] = ['uri-id'];
}
$items = Post::selectForUser($uid, ['uri-id'], $condition, $params);
$statuses = [];
while ($item = Post::fetch($items)) {
$statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid);
}
DBA::close($items);
if (!empty($min_id)) {
array_reverse($statuses);
}
System::jsonExit($statuses);
}
}

View File

@ -0,0 +1,91 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon\Timelines;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Post;
use Friendica\Module\BaseApi;
use Friendica\Network\HTTPException;
/**
* @see https://docs.joinmastodon.org/methods/timelines/
*/
class ListTimeline extends BaseApi
{
/**
* @param array $parameters
* @throws HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
self::login();
$uid = self::getCurrentUserID();
if (empty($parameters['id'])) {
DI::mstdnError()->UnprocessableEntity();
}
// Return results older than id
$max_id = (int)!isset($_REQUEST['max_id']) ? 0 : $_REQUEST['max_id'];
// Return results newer than id
$since_id = (int)!isset($_REQUEST['since_id']) ? 0 : $_REQUEST['since_id'];
// Return results immediately newer than id
$min_id = (int)!isset($_REQUEST['min_id']) ? 0 : $_REQUEST['min_id'];
// Maximum number of results to return. Defaults to 20.
$limit = (int)!isset($_REQUEST['limit']) ? 20 : $_REQUEST['limit'];
$params = ['order' => ['uri-id' => true], 'limit' => $limit];
$condition = ["`uid` = ? AND `gravity` IN (?, ?) AND `contact-id` IN (SELECT `contact-id` FROM `group_member` WHERE `gid` = ?)",
$uid, GRAVITY_PARENT, GRAVITY_COMMENT, $parameters['id']];
if (!empty($max_id)) {
$condition = DBA::mergeConditions($condition, ["`uri-id` < ?", $max_id]);
}
if (!empty($since_id)) {
$condition = DBA::mergeConditions($condition, ["`uri-id` > ?", $since_id]);
}
if (!empty($min_id)) {
$condition = DBA::mergeConditions($condition, ["`uri-id` > ?", $min_id]);
$params['order'] = ['uri-id'];
}
$items = Post::selectForUser($uid, ['uri-id'], $condition, $params);
$statuses = [];
while ($item = Post::fetch($items)) {
$statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid);
}
DBA::close($items);
if (!empty($min_id)) {
array_reverse($statuses);
}
System::jsonExit($statuses);
}
}

View File

@ -46,7 +46,7 @@ class PublicTimeline extends BaseApi
// Show only remote statuses? Defaults to false.
$remote = (bool)!isset($_REQUEST['remote']) ? false : ($_REQUEST['remote'] == 'true');
// Show only statuses with media attached? Defaults to false.
$only_media = (bool)!isset($_REQUEST['only_media']) ? false : ($_REQUEST['only_media'] == 'true'); // Currently not supported
$only_media = (bool)!isset($_REQUEST['only_media']) ? false : ($_REQUEST['only_media'] == 'true');
// Return results older than this id
$max_id = (int)!isset($_REQUEST['max_id']) ? 0 : $_REQUEST['max_id'];
// Return results newer than this id
@ -69,6 +69,11 @@ class PublicTimeline extends BaseApi
$condition = DBA::mergeConditions($condition, ["NOT `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `origin`)"]);
}
if ($only_media) {
$condition = DBA::mergeConditions($condition, ["`uri-id` IN (SELECT `uri-id` FROM `post-media` WHERE `type` IN (?, ?, ?))",
Post\Media::AUDIO, Post\Media::IMAGE, Post\Media::VIDEO]);
}
if (!empty($max_id)) {
$condition = DBA::mergeConditions($condition, ["`uri-id` < ?", $max_id]);
}

View File

@ -0,0 +1,106 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Api\Mastodon\Timelines;
use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Post;
use Friendica\Module\BaseApi;
use Friendica\Network\HTTPException;
/**
* @see https://docs.joinmastodon.org/methods/timelines/
*/
class Tag extends BaseApi
{
/**
* @param array $parameters
* @throws HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
self::login();
$uid = self::getCurrentUserID();
if (empty($parameters['hashtag'])) {
DI::mstdnError()->UnprocessableEntity();
}
// If true, return only local statuses. Defaults to false.
$local = (bool)!isset($_REQUEST['local']) ? false : ($_REQUEST['local'] == 'true');
// If true, return only statuses with media attachments. Defaults to false.
$only_media = (bool)!isset($_REQUEST['only_media']) ? false : ($_REQUEST['only_media'] == 'true');
// Return results older than this ID.
$max_id = (int)!isset($_REQUEST['max_id']) ? 0 : $_REQUEST['max_id'];
// Return results newer than this ID.
$since_id = (int)!isset($_REQUEST['since_id']) ? 0 : $_REQUEST['since_id'];
// Return results immediately newer than this ID.
$min_id = (int)!isset($_REQUEST['min_id']) ? 0 : $_REQUEST['min_id'];
// Maximum number of results to return. Defaults to 20.
$limit = (int)!isset($_REQUEST['limit']) ? 20 : $_REQUEST['limit'];
$params = ['order' => ['uri-id' => true], 'limit' => $limit];
$condition = ["`name` = ? AND (`uid` = ? OR (`uid` = ? AND NOT `global`))
AND (`network` IN (?, ?, ?, ?) OR (`uid` = ? AND `uid` != ?))",
$parameters['hashtag'], 0, $uid, Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, $uid, 0];
if ($local) {
$condition = DBA::mergeConditions($condition, ["`uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `origin`)"]);
}
if ($only_media) {
$condition = DBA::mergeConditions($condition, ["`uri-id` IN (SELECT `uri-id` FROM `post-media` WHERE `type` IN (?, ?, ?))",
Post\Media::AUDIO, Post\Media::IMAGE, Post\Media::VIDEO]);
}
if (!empty($max_id)) {
$condition = DBA::mergeConditions($condition, ["`uri-id` < ?", $max_id]);
}
if (!empty($since_id)) {
$condition = DBA::mergeConditions($condition, ["`uri-id` > ?", $since_id]);
}
if (!empty($min_id)) {
$condition = DBA::mergeConditions($condition, ["`uri-id` > ?", $min_id]);
$params['order'] = ['uri-id'];
}
$items = DBA::select('tag-search-view', ['uri-id'], $condition, $params);
$statuses = [];
while ($item = Post::fetch($items)) {
$statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid);
}
DBA::close($items);
if (!empty($min_id)) {
array_reverse($statuses);
}
System::jsonExit($statuses);
}
}

View File

@ -21,9 +21,6 @@
namespace Friendica\Module\Api\Mastodon;
use Friendica\Core\Logger;
use Friendica\Core\System;
use Friendica\DI;
use Friendica\Module\BaseApi;
/**
@ -31,17 +28,48 @@ use Friendica\Module\BaseApi;
*/
class Unimplemented extends BaseApi
{
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function delete(array $parameters = [])
{
self::unsupported('delete');
}
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function patch(array $parameters = [])
{
self::unsupported('patch');
}
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function post(array $parameters = [])
{
self::unsupported('post');
}
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function put(array $parameters = [])
{
self::unsupported('put');
}
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
$path = DI::args()->getQueryString();
Logger::info('Unimplemented API call', ['path' => $path]);
$error = DI::l10n()->t('API endpoint "%s" is not implemented', $path);
$error_description = DI::l10n()->t('The API endpoint is currently not implemented but might be in the future.');;
$errorobj = new \Friendica\Object\Api\Mastodon\Error($error, $error_description);
System::jsonError(501, $errorobj->toArray());
self::unsupported('get');
}
}

View File

@ -22,8 +22,14 @@
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Core\Logger;
use Friendica\Core\System;
use Friendica\Database\Database;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Network\HTTPException;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Network;
require_once __DIR__ . '/../../include/api.php';
@ -53,6 +59,32 @@ class BaseApi extends BaseModule
}
}
public static function delete(array $parameters = [])
{
if (!api_user()) {
throw new HTTPException\UnauthorizedException(DI::l10n()->t('Permission denied.'));
}
$a = DI::app();
if (!empty($a->user['uid']) && $a->user['uid'] != api_user()) {
throw new HTTPException\ForbiddenException(DI::l10n()->t('Permission denied.'));
}
}
public static function patch(array $parameters = [])
{
if (!api_user()) {
throw new HTTPException\UnauthorizedException(DI::l10n()->t('Permission denied.'));
}
$a = DI::app();
if (!empty($a->user['uid']) && $a->user['uid'] != api_user()) {
throw new HTTPException\ForbiddenException(DI::l10n()->t('Permission denied.'));
}
}
public static function post(array $parameters = [])
{
if (!api_user()) {
@ -66,6 +98,74 @@ class BaseApi extends BaseModule
}
}
public static function put(array $parameters = [])
{
if (!api_user()) {
throw new HTTPException\UnauthorizedException(DI::l10n()->t('Permission denied.'));
}
$a = DI::app();
if (!empty($a->user['uid']) && $a->user['uid'] != api_user()) {
throw new HTTPException\ForbiddenException(DI::l10n()->t('Permission denied.'));
}
}
/**
* Quit execution with the message that the endpoint isn't implemented
*
* @param string $method
* @return void
*/
public static function unsupported(string $method = 'all')
{
$path = DI::args()->getQueryString();
Logger::info('Unimplemented API call', ['method' => $method, 'path' => $path, 'agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', 'request' => $_REQUEST ?? []]);
$error = DI::l10n()->t('API endpoint %s %s is not implemented', strtoupper($method), $path);
$error_description = DI::l10n()->t('The API endpoint is currently not implemented but might be in the future.');
$errorobj = new \Friendica\Object\Api\Mastodon\Error($error, $error_description);
System::jsonError(501, $errorobj->toArray());
}
/**
* Get post data that is transmitted as JSON
*
* @return array request data
*/
public static function getJsonPostData()
{
$postdata = Network::postdata();
if (empty($postdata)) {
return [];
}
return json_decode($postdata, true);
}
/**
* Get request data for put requests
*
* @return array request data
*/
public static function getPutData()
{
$rawdata = Network::postdata();
if (empty($rawdata)) {
return [];
}
$putdata = [];
foreach (explode('&', $rawdata) as $value) {
$data = explode('=', $value);
if (count($data) == 2) {
$putdata[$data[0]] = urldecode($data[1]);
}
}
return $putdata;
}
/**
* Log in user via OAuth1 or Simple HTTP Auth.
*
@ -84,13 +184,138 @@ class BaseApi extends BaseModule
*/
protected static function login()
{
api_login(DI::app());
if (empty(self::$current_user_id)) {
self::$current_user_id = self::getUserByBearer();
}
if (empty(self::$current_user_id)) {
// The execution stops here if no one is logged in
api_login(DI::app());
}
self::$current_user_id = api_user();
return (bool)self::$current_user_id;
}
/**
* Get current user id, returns 0 if not logged in
*
* @return int User ID
*/
protected static function getCurrentUserID()
{
if (empty(self::$current_user_id)) {
self::$current_user_id = self::getUserByBearer();
}
if (empty(self::$current_user_id)) {
// Fetch the user id if logged in - but don't fail if not
api_login(DI::app(), false);
self::$current_user_id = api_user();
}
return (int)self::$current_user_id;
}
/**
* Get the user id via the Bearer token
*
* @return int User-ID
*/
private static function getUserByBearer()
{
$authorization = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
if (substr($authorization, 0, 7) != 'Bearer ') {
return 0;
}
$bearer = trim(substr($authorization, 7));
$condition = ['access_token' => $bearer];
$token = DBA::selectFirst('application-token', ['uid'], $condition);
if (!DBA::isResult($token)) {
Logger::warning('Token not found', $condition);
return 0;
}
Logger::info('Token found', $token);
return $token['uid'];
}
/**
* Get the application record via the proved request header fields
*
* @param string $client_id
* @param string $client_secret
* @param string $redirect_uri
* @return array application record
*/
public static function getApplication(string $client_id, string $client_secret, string $redirect_uri)
{
$condition = ['client_id' => $client_id];
if (!empty($client_secret)) {
$condition['client_secret'] = $client_secret;
}
if (!empty($redirect_uri)) {
$condition['redirect_uri'] = $redirect_uri;
}
$application = DBA::selectFirst('application', [], $condition);
if (!DBA::isResult($application)) {
Logger::warning('Application not found', $condition);
return [];
}
return $application;
}
/**
* Check if an token for the application and user exists
*
* @param array $application
* @param integer $uid
* @return boolean
*/
public static function existsTokenForUser(array $application, int $uid)
{
return DBA::exists('application-token', ['application-id' => $application['id'], 'uid' => $uid]);
}
/**
* Fetch the token for the given application and user
*
* @param array $application
* @param integer $uid
* @return array application record
*/
public static function getTokenForUser(array $application, int $uid)
{
return DBA::selectFirst('application-token', [], ['application-id' => $application['id'], 'uid' => $uid]);
}
/**
* Create and fetch an token for the application and user
*
* @param array $application
* @param integer $uid
* @param string $scope
* @return array application record
*/
public static function createTokenForUser(array $application, int $uid, string $scope)
{
$code = bin2hex(random_bytes(32));
$access_token = bin2hex(random_bytes(32));
$fields = ['application-id' => $application['id'], 'uid' => $uid, 'code' => $code, 'access_token' => $access_token, 'scopes' => $scope,
'read' => (stripos($scope, 'read') !== false), 'write' => (stripos($scope, 'write') !== false),
'follow' => (stripos($scope, 'follow') !== false), 'created_at' => DateTimeFormat::utcNow(DateTimeFormat::MYSQL)];
if (!DBA::insert('application-token', $fields, Database::INSERT_UPDATE)) {
return [];
}
return DBA::selectFirst('application-token', [], ['application-id' => $application['id'], 'uid' => $uid]);
}
/**
* Get user info array.
*
@ -140,7 +365,7 @@ class BaseApi extends BaseModule
$return = '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . $return;
break;
}
return $return;
}

View File

@ -413,6 +413,10 @@ class Contact extends BaseModule
}
if ($cmd === 'block') {
if (public_contact() === $contact_id) {
throw new BadRequestException(DI::l10n()->t('You can\'t block yourself'));
}
self::blockContact($contact_id);
$blocked = Model\Contact\User::isBlocked($contact_id, local_user());
@ -423,6 +427,10 @@ class Contact extends BaseModule
}
if ($cmd === 'ignore') {
if (public_contact() === $contact_id) {
throw new BadRequestException(DI::l10n()->t('You can\'t ignore yourself'));
}
self::ignoreContact($contact_id);
$ignored = Model\Contact\User::isIgnored($contact_id, local_user());

View File

@ -0,0 +1,53 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\OAuth;
use Friendica\Core\Renderer;
use Friendica\DI;
use Friendica\Module\BaseApi;
/**
* Acknowledgement of OAuth requests
*/
class Acknowledge extends BaseApi
{
public static function post(array $parameters = [])
{
DI::session()->set('oauth_acknowledge', true);
DI::app()->redirect(DI::session()->get('return_path'));
}
public static function content(array $parameters = [])
{
DI::session()->set('return_path', $_REQUEST['return_path'] ?? '');
$o = Renderer::replaceMacros(Renderer::getMarkupTemplate('oauth_authorize.tpl'), [
'$title' => DI::l10n()->t('Authorize application connection'),
'$app' => ['name' => $_REQUEST['application'] ?? ''],
'$authorize' => DI::l10n()->t('Do you want to authorize this application to access your posts and contacts, and/or create new posts for you?'),
'$yes' => DI::l10n()->t('Yes'),
'$no' => DI::l10n()->t('No'),
]);
return $o;
}
}

View File

@ -0,0 +1,90 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\OAuth;
use Friendica\Core\Logger;
use Friendica\DI;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/spec/oauth/
* @see https://aaronparecki.com/oauth-2-simplified/
*/
class Authorize extends BaseApi
{
/**
* @param array $parameters
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function rawContent(array $parameters = [])
{
$response_type = $_REQUEST['response_type'] ?? '';
$client_id = $_REQUEST['client_id'] ?? '';
$client_secret = $_REQUEST['client_secret'] ?? ''; // Isn't normally provided. We will use it if present.
$redirect_uri = $_REQUEST['redirect_uri'] ?? '';
$scope = $_REQUEST['scope'] ?? 'read';
$state = $_REQUEST['state'] ?? '';
if ($response_type != 'code') {
Logger::warning('Unsupported or missing response type', ['request' => $_REQUEST]);
DI::mstdnError()->UnprocessableEntity(DI::l10n()->t('Unsupported or missing response type'));
}
if (empty($client_id) || empty($redirect_uri)) {
Logger::warning('Incomplete request data', ['request' => $_REQUEST]);
DI::mstdnError()->UnprocessableEntity(DI::l10n()->t('Incomplete request data'));
}
$application = self::getApplication($client_id, $client_secret, $redirect_uri);
if (empty($application)) {
DI::mstdnError()->UnprocessableEntity();
}
// @todo Compare the application scope and requested scope
$request = $_REQUEST;
unset($request['pagename']);
$redirect = 'oauth/authorize?' . http_build_query($request);
$uid = local_user();
if (empty($uid)) {
Logger::info('Redirect to login');
DI::app()->redirect('login?return_path=' . urlencode($redirect));
} else {
Logger::info('Already logged in user', ['uid' => $uid]);
}
if (!self::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::session()->remove('oauth_acknowledge');
$token = self::createTokenForUser($application, $uid, $scope);
if (!$token) {
DI::mstdnError()->UnprocessableEntity();
}
DI::app()->redirect($application['redirect_uri'] . (strpos($application['redirect_uri'], '?') ? '&' : '?') . http_build_query(['code' => $token['code'], 'state' => $state]));
}
}

View File

@ -0,0 +1,35 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\OAuth;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/spec/oauth/
*/
class Revoke extends BaseApi
{
public static function post(array $parameters = [])
{
self::unsupported('post');
}
}

View File

@ -0,0 +1,86 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\OAuth;
use Friendica\Core\Logger;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/spec/oauth/
* @see https://aaronparecki.com/oauth-2-simplified/
*/
class Token extends BaseApi
{
public static function post(array $parameters = [])
{
$grant_type = $_REQUEST['grant_type'] ?? '';
$code = $_REQUEST['code'] ?? '';
$redirect_uri = $_REQUEST['redirect_uri'] ?? '';
$client_id = $_REQUEST['client_id'] ?? '';
$client_secret = $_REQUEST['client_secret'] ?? '';
// AndStatus transmits the client data in the AUTHORIZATION header field, see https://github.com/andstatus/andstatus/issues/530
if (empty($client_id) && !empty($_SERVER['HTTP_AUTHORIZATION']) && (substr($_SERVER['HTTP_AUTHORIZATION'], 0, 6) == 'Basic ')) {
$datapair = explode(':', base64_decode(trim(substr($_SERVER['HTTP_AUTHORIZATION'], 6))));
if (count($datapair) == 2) {
$client_id = $datapair[0];
$client_secret = $datapair[1];
}
}
if (empty($client_id) || empty($client_secret)) {
Logger::warning('Incomplete request data', ['request' => $_REQUEST]);
DI::mstdnError()->UnprocessableEntity(DI::l10n()->t('Incomplete request data'));
}
$application = self::getApplication($client_id, $client_secret, $redirect_uri);
if (empty($application)) {
DI::mstdnError()->UnprocessableEntity();
}
if ($grant_type == 'client_credentials') {
// the "client_credentials" are used as a token for the application itself.
// see https://aaronparecki.com/oauth-2-simplified/#client-credentials
$token = self::createTokenForUser($application, 0, '');
} elseif ($grant_type == 'authorization_code') {
// For security reasons only allow freshly created tokens
$condition = ["`redirect_uri` = ? AND `id` = ? AND `code` = ? AND `created_at` > UTC_TIMESTAMP() - INTERVAL ? MINUTE",
$redirect_uri, $application['id'], $code, 5];
$token = DBA::selectFirst('application-view', ['access_token', 'created_at'], $condition);
if (!DBA::isResult($token)) {
Logger::warning('Token not found or outdated', $condition);
DI::mstdnError()->Unauthorized();
}
} else {
Logger::warning('Unsupported or missing grant type', ['request' => $_REQUEST]);
DI::mstdnError()->UnprocessableEntity(DI::l10n()->t('Unsupported or missing grant type'));
}
$object = new \Friendica\Object\Api\Mastodon\Token($token['access_token'], 'Bearer', $application['scopes'], $token['created_at']);
System::jsonExit($object->toArray());
}
}

View File

@ -34,7 +34,6 @@ use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Item;
use Friendica\Model\ItemContent;
use Friendica\Model\Post;
use Friendica\Model\Tag;
use Friendica\Module\BaseSearch;

View File

@ -36,8 +36,12 @@ class Login extends BaseModule
{
public static function content(array $parameters = [])
{
$return_path = $_REQUEST['return_path'] ?? '' ;
if (local_user()) {
DI::baseUrl()->redirect();
DI::baseUrl()->redirect($return_path);
} elseif (!empty($return_path)) {
Session::set('return_path', $return_path);
}
return self::form(Session::get('return_path'), intval(DI::config()->get('config', 'register_policy')) !== \Friendica\Module\Register::CLOSED);

View File

@ -30,8 +30,18 @@ use Friendica\BaseDataTransferObject;
*/
class Application extends BaseDataTransferObject
{
/** @var string */
protected $client_id;
/** @var string */
protected $client_secret;
/** @var int */
protected $id;
/** @var string */
protected $name;
/** @var string */
protected $redirect_uri;
/** @var string */
protected $website;
/**
* Creates an application entry
@ -39,8 +49,36 @@ class Application extends BaseDataTransferObject
* @param array $item
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public function __construct(string $name)
public function __construct(string $name, string $client_id = null, string $client_secret = null, int $id = null, string $redirect_uri = null, string $website = null)
{
$this->name = $name;
$this->client_id = $client_id;
$this->client_secret = $client_secret;
$this->id = $id;
$this->name = $name;
$this->redirect_uri = $redirect_uri;
$this->website = $website;
}
/**
* Returns the current entity as an array
*
* @return array
*/
public function toArray(): array
{
$application = parent::toArray();
if (empty($application['id'])) {
unset($application['client_id']);
unset($application['client_secret']);
unset($application['id']);
unset($application['redirect_uri']);
}
if (empty($application['website'])) {
unset($application['website']);
}
return $application;
}
}

View File

@ -39,9 +39,17 @@ class Card extends BaseDataTransferObject
/** @var string */
protected $type;
/** @var string */
protected $author_name;
/** @var string */
protected $author_url;
/** @var string */
protected $provider_name;
/** @var string */
protected $provider_url;
/** @var int */
protected $width;
/** @var int */
protected $height;
/** @var string */
protected $image;
@ -57,9 +65,13 @@ class Card extends BaseDataTransferObject
$this->title = $attachment['title'] ?? '';
$this->description = $attachment['description'] ?? '';
$this->type = $attachment['type'] ?? '';
$this->image = $attachment['image'] ?? '';
$this->author_name = $attachment['author_name'] ?? '';
$this->author_url = $attachment['author_url'] ?? '';
$this->provider_name = $attachment['provider_name'] ?? '';
$this->provider_url = $attachment['provider_url'] ?? '';
$this->width = $attachment['width'] ?? 0;
$this->height = $attachment['height'] ?? 0;
$this->image = $attachment['image'] ?? '';
}
/**

View File

@ -80,7 +80,7 @@ class Instance extends BaseDataTransferObject
$instance->description = DI::config()->get('config', 'info');
$instance->email = DI::config()->get('config', 'admin_email');
$instance->version = FRIENDICA_VERSION;
$instance->urls = []; // Not supported
$instance->urls = null; // Not supported
$instance->stats = Stats::get();
$instance->thumbnail = $baseUrl->get() . (DI::config()->get('system', 'shortcut_icon') ?? 'images/friendica-32.png');
$instance->languages = [DI::config()->get('system', 'language')];

View File

@ -0,0 +1,51 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Object\Api\Mastodon;
use Friendica\BaseDataTransferObject;
/**
* Class ListEntity
*
* @see https://docs.joinmastodon.org/entities/list/
*/
class ListEntity extends BaseDataTransferObject
{
/** @var string */
protected $id;
/** @var string */
protected $title;
/**
* Creates an list record
*
* @param int $id
* @param string $title
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public function __construct(int $id, string $title, string $policy)
{
$this->id = (string)$id;
$this->title = $title;
$this->replies_policy = $policy;
}
}

View File

@ -0,0 +1,78 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Object\Api\Mastodon;
use Friendica\BaseDataTransferObject;
use Friendica\Util\DateTimeFormat;
/**
* Class Notification
*
* @see https://docs.joinmastodon.org/entities/notification/
*/
class Notification extends BaseDataTransferObject
{
/** @var string */
protected $id;
/** @var string (Enumerable oneOf) */
protected $type;
/** @var string (Datetime) */
protected $created_at;
/** @var Account */
protected $account;
/** @var Status|null */
protected $status = null;
/**
* Creates a notification record
*
* @param array $item
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public function __construct(int $id, string $type, string $created_at, Account $account = null, Status $status = null)
{
$this->id = (string)$id;
$this->type = $type;
$this->created_at = DateTimeFormat::utc($created_at, DateTimeFormat::ATOM);
$this->account = $account->toArray();
if (!empty($status)) {
$this->status = $status->toArray();
}
}
/**
* Returns the current entity as an array
*
* @return array
*/
public function toArray(): array
{
$notification = parent::toArray();
if (!$notification['status']) {
unset($notification['status']);
}
return $notification;
}
}

View File

@ -0,0 +1,62 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Object\Api\Mastodon;
use Friendica\App\BaseURL;
use Friendica\BaseDataTransferObject;
/**
* Class Preferences
*
* @see https://docs.joinmastodon.org/entities/preferences/
*/
class Preferences extends BaseDataTransferObject
{
// /** @var string (Enumerable, oneOf) */
// protected $posting_default_visibility;
// /** @var bool */
// protected $posting_default_sensitive;
// /** @var string (ISO 639-1 language two-letter code), or null*/
// protected $posting_default_language;
// /** @var string (Enumerable, oneOf) */
// protected $reading_expand_media;
// /** @var bool */
// protected $reading_expand_spoilers;
/**
* Creates a preferences record.
*
* @param BaseURL $baseUrl
* @param array $publicContact Full contact table record with uid = 0
* @param array $apcontact Optional full apcontact table record
* @param array $userContact Optional full contact table record with uid != 0
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public function __construct(string $visibility, bool $sensitive, string $language, string $media, bool $spoilers)
{
$this->{'posting:default:visibility'} = $visibility;
$this->{'posting:default:sensitive'} = $sensitive;
$this->{'posting:default:language'} = $language;
$this->{'reading:expand:media'} = $media;
$this->{'reading:expand:spoilers'} = $spoilers;
}
}

View File

@ -126,7 +126,7 @@ class Status extends BaseDataTransferObject
$this->muted = $userAttributes->muted;
$this->bookmarked = $userAttributes->bookmarked;
$this->pinned = $userAttributes->pinned;
$this->content = BBCode::convert($item['raw-body'] ?? $item['body'], false);
$this->content = BBCode::convert($item['raw-body'] ?? $item['body'], false, BBCode::API);
$this->reblog = $reblog;
$this->application = $application->toArray();
$this->account = $account->toArray();
@ -134,7 +134,7 @@ class Status extends BaseDataTransferObject
$this->mentions = $mentions;
$this->tags = $tags;
$this->emojis = [];
$this->card = $card->toArray();
$this->card = $card->toArray() ?: null;
$this->poll = null;
}

View File

@ -0,0 +1,58 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, 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\Object\Api\Mastodon;
use Friendica\BaseDataTransferObject;
use Friendica\Util\DateTimeFormat;
/**
* Class Error
*
* @see https://docs.joinmastodon.org/entities/error
*/
class Token extends BaseDataTransferObject
{
/** @var string */
protected $access_token;
/** @var string */
protected $token_type;
/** @var string */
protected $scope;
/** @var string (Datetime) */
protected $created_at;
/**
* Creates a token record
*
* @param string $access_token
* @param string $token_type
* @param string $scope
* @param string $created_at
*/
public function __construct(string $access_token, string $token_type, string $scope, string $created_at)
{
$this->access_token = $access_token;
$this->token_type = $token_type;
$this->scope = $scope;
$this->created_at = DateTimeFormat::utc($created_at, DateTimeFormat::ATOM);
}
}

View File

@ -211,7 +211,7 @@ class Post
$origin = $item['origin'] || $item['parent-origin'];
if ($item['pinned']) {
$pinned = DI::l10n()->t('pinned item');
$pinned = DI::l10n()->t('Pinned item');
}
// Showing the one or the other text, depending upon if we can only hide it or really delete it.
@ -223,9 +223,12 @@ class Post
$drop = [
'dropping' => $dropping,
'pagedrop' => $item['pagedrop'],
'select' => DI::l10n()->t('Select'),
'delete' => $delete,
'select' => DI::l10n()->t('Select'),
'delete' => $delete,
];
}
if (!$item['self']) {
$block = [
'blocking' => true,
'block' => DI::l10n()->t('Block %s', $item['author-name']),
@ -233,7 +236,7 @@ class Post
];
}
$filer = (($conv->getProfileOwner() == local_user() && ($item['uid'] != 0)) ? DI::l10n()->t("save to folder") : false);
$filer = (($conv->getProfileOwner() == local_user() && ($item['uid'] != 0)) ? DI::l10n()->t('Save to folder') : false);
$profile_name = $item['author-name'];
if (!empty($item['author-link']) && empty($item['author-name'])) {
@ -297,12 +300,12 @@ class Post
$ignored = PostModel\ThreadUser::getIgnored($item['uri-id'], local_user());
if ($item['mention'] || $ignored) {
$ignore = [
'do' => DI::l10n()->t("ignore thread"),
'undo' => DI::l10n()->t("unignore thread"),
'toggle' => DI::l10n()->t("toggle ignore status"),
'do' => DI::l10n()->t('Ignore thread'),
'undo' => DI::l10n()->t('Unignore thread'),
'toggle' => DI::l10n()->t('Toggle ignore status'),
'classdo' => $ignored ? "hidden" : "",
'classundo' => $ignored ? "" : "hidden",
'ignored' => DI::l10n()->t('ignored'),
'ignored' => DI::l10n()->t('Ignored'),
];
}
@ -311,28 +314,28 @@ class Post
$ispinned = ($item['pinned'] ? 'pinned' : 'unpinned');
$pin = [
'do' => DI::l10n()->t('pin'),
'undo' => DI::l10n()->t('unpin'),
'toggle' => DI::l10n()->t('toggle pin status'),
'do' => DI::l10n()->t('Pin'),
'undo' => DI::l10n()->t('Unpin'),
'toggle' => DI::l10n()->t('Toggle pin status'),
'classdo' => $item['pinned'] ? 'hidden' : '',
'classundo' => $item['pinned'] ? '' : 'hidden',
'pinned' => DI::l10n()->t('pinned'),
'pinned' => DI::l10n()->t('Pinned'),
];
}
$isstarred = (($item['starred']) ? "starred" : "unstarred");
$star = [
'do' => DI::l10n()->t("add star"),
'undo' => DI::l10n()->t("remove star"),
'toggle' => DI::l10n()->t("toggle star status"),
'do' => DI::l10n()->t('Add star'),
'undo' => DI::l10n()->t('Remove star'),
'toggle' => DI::l10n()->t('Toggle star status'),
'classdo' => $item['starred'] ? "hidden" : "",
'classundo' => $item['starred'] ? "" : "hidden",
'starred' => DI::l10n()->t('starred'),
'starred' => DI::l10n()->t('Starred'),
];
$tagger = [
'add' => DI::l10n()->t("add tag"),
'add' => DI::l10n()->t('Add tag'),
'class' => "",
];
}
@ -342,8 +345,8 @@ class Post
}
if ($conv->isWritable()) {
$buttons['like'] = [DI::l10n()->t("I like this \x28toggle\x29") , DI::l10n()->t("like")];
$buttons['dislike'] = [DI::l10n()->t("I don't like this \x28toggle\x29"), DI::l10n()->t("dislike")];
$buttons['like'] = [DI::l10n()->t("I like this \x28toggle\x29") , DI::l10n()->t('Like')];
$buttons['dislike'] = [DI::l10n()->t("I don't like this \x28toggle\x29"), DI::l10n()->t('Dislike')];
if ($shareable) {
$buttons['share'] = [DI::l10n()->t('Quote share this'), DI::l10n()->t('Quote Share')];
}
@ -399,7 +402,7 @@ class Post
// Fetching of Diaspora posts doesn't always work. There are issues with reshares and possibly comments
if (($item['network'] != Protocol::DIASPORA) && empty($comment) && !empty(Session::get('remote_comment'))) {
$remote_comment = [DI::l10n()->t('Comment this item on your system'), DI::l10n()->t('remote comment'),
$remote_comment = [DI::l10n()->t('Comment this item on your system'), DI::l10n()->t('Remote comment'),
str_replace('{uri}', urlencode($item['uri']), Session::get('remote_comment'))];
} else {
$remote_comment = '';
@ -424,6 +427,8 @@ class Post
$tmp_item = [
'template' => $this->getTemplate(),
'type' => implode("", array_slice(explode("/", $item['verb']), -1)),
'comment_firstcollapsed' => false,
'comment_lastcollapsed' => false,
'suppress_tags' => DI::config()->get('system', 'suppress_tags'),
'tags' => $tags['tags'],
'hashtags' => $tags['hashtags'],
@ -540,10 +545,7 @@ class Post
}
}
if ($this->isToplevel()) {
$result['total_comments_num'] = "$total_children";
$result['total_comments_text'] = DI::l10n()->tt('comment', 'comments', $total_children);
}
$result['total_comments_num'] = $this->isToplevel() ? $total_children : 0;
$result['private'] = $item['private'];
$result['toplevel'] = ($this->isToplevel() ? 'toplevel_item' : '');
@ -884,8 +886,13 @@ class Post
$terms = Tag::getByURIId($item['uri-id'], [Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION]);
foreach ($terms as $term) {
if (!$term['url']) {
DI::logger()->warning('Mention term with no URL', ['term' => $term]);
continue;
}
$profile = Contact::getByURL($term['url'], false, ['addr', 'contact-type']);
if (!empty($profile['addr']) && ((($profile['contact-type'] ?? '') ?: Contact::TYPE_UNKNOWN) != Contact::TYPE_COMMUNITY) &&
if (!empty($profile['addr']) && (($profile['contact-type'] ?? Contact::TYPE_UNKNOWN) != Contact::TYPE_COMMUNITY) &&
($profile['addr'] != $owner['addr']) && !strstr($text, $profile['addr'])) {
$text .= '@' . $profile['addr'] . ' ';
}
@ -942,9 +949,9 @@ class Post
'$qcomment' => $qcomment,
'$default' => $default_text,
'$profile_uid' => $uid,
'$mylink' => DI::baseUrl()->remove($a->contact['url']),
'$mylink' => DI::baseUrl()->remove($a->contact['url'] ?? ''),
'$mytitle' => DI::l10n()->t('This is you'),
'$myphoto' => DI::baseUrl()->remove($a->contact['thumb']),
'$myphoto' => DI::baseUrl()->remove($a->contact['thumb'] ?? ''),
'$comment' => DI::l10n()->t('Comment'),
'$submit' => DI::l10n()->t('Submit'),
'$loading' => DI::l10n()->t('Loading...'),

View File

@ -21,7 +21,6 @@
namespace Friendica\Protocol\ActivityPub;
use Friendica\Content\PageInfo;
use Friendica\Content\Text\BBCode;
use Friendica\Content\Text\HTML;
use Friendica\Content\Text\Markdown;
@ -464,6 +463,7 @@ class Processor
if (!empty($activity['source'])) {
$item['body'] = $activity['source'];
$item['raw-body'] = $content;
$item['body'] = Item::improveSharedDataInBody($item);
} else {
if (empty($activity['directmessage']) && ($item['thr-parent'] != $item['uri']) && ($item['gravity'] == GRAVITY_COMMENT)) {
$item_private = !in_array(0, $activity['item_receiver']);

View File

@ -111,9 +111,12 @@ class Receiver
}
$http_signer = HTTPSignature::getSigner($body, $header);
if (empty($http_signer)) {
if ($http_signer === false) {
Logger::warning('Invalid HTTP signature, message will be discarded.');
return;
} elseif (empty($http_signer)) {
Logger::info('Signer is a tombstone. The message will be discarded, the signer account is deleted.');
return;
} else {
Logger::info('Valid HTTP signature', ['signer' => $http_signer]);
}

View File

@ -1260,53 +1260,60 @@ class Transmitter
{
$attachments = [];
// Currently deactivated, since it creates side effects on Mastodon and Pleroma.
// It will be reactivated, once this cleared.
/*
$attach_data = BBCode::getAttachmentData($item['body']);
if (!empty($attach_data['url'])) {
$attachment = ['type' => 'Page',
'mediaType' => 'text/html',
'url' => $attach_data['url']];
if (!empty($attach_data['title'])) {
$attachment['name'] = $attach_data['title'];
$uriids = [$item['uri-id']];
$shared = BBCode::fetchShareAttributes($item['body']);
if (!empty($shared['guid'])) {
$shared_item = Post::selectFirst(['uri-id'], ['guid' => $shared['guid']]);
if (!empty($shared_item['uri-id'])) {
$uriids[] = $shared_item['uri-id'];
}
if (!empty($attach_data['description'])) {
$attachment['summary'] = $attach_data['description'];
}
if (!empty($attach_data['image'])) {
$imgdata = Images::getInfoFromURLCached($attach_data['image']);
if ($imgdata) {
$attachment['icon'] = ['type' => 'Image',
'mediaType' => $imgdata['mime'],
'width' => $imgdata[0],
'height' => $imgdata[1],
'url' => $attach_data['image']];
}
}
$attachments[] = $attachment;
}
*/
foreach (Post\Media::getByURIId($item['uri-id'], [Post\Media::DOCUMENT, Post\Media::TORRENT, Post\Media::UNKNOWN]) as $attachment) {
$attachments[] = ['type' => 'Document',
'mediaType' => $attachment['mimetype'],
'url' => $attachment['url'],
'name' => $attachment['description']];
$urls = [];
foreach ($uriids as $uriid) {
foreach (Post\Media::getByURIId($uriid, [Post\Media::DOCUMENT, Post\Media::TORRENT]) as $attachment) {
if (in_array($attachment['url'], $urls)) {
continue;
}
$urls[] = $attachment['url'];
$attachments[] = ['type' => 'Document',
'mediaType' => $attachment['mimetype'],
'url' => $attachment['url'],
'name' => $attachment['description']];
}
}
if ($type != 'Note') {
return $attachments;
}
foreach (Post\Media::getByURIId($item['uri-id'], [Post\Media::AUDIO, Post\Media::IMAGE, Post\Media::VIDEO]) as $attachment) {
$attachments[] = ['type' => 'Document',
'mediaType' => $attachment['mimetype'],
'url' => $attachment['url'],
'name' => $attachment['description']];
foreach ($uriids as $uriid) {
foreach (Post\Media::getByURIId($uriid, [Post\Media::AUDIO, Post\Media::IMAGE, Post\Media::VIDEO]) as $attachment) {
if (in_array($attachment['url'], $urls)) {
continue;
}
$urls[] = $attachment['url'];
$attachments[] = ['type' => 'Document',
'mediaType' => $attachment['mimetype'],
'url' => $attachment['url'],
'name' => $attachment['description']];
}
// Currently deactivated, since it creates side effects on Mastodon and Pleroma.
// It will be activated, once this cleared.
/*
foreach (Post\Media::getByURIId($uriid, [Post\Media::HTML]) as $attachment) {
if (in_array($attachment['url'], $urls)) {
continue;
}
$urls[] = $attachment['url'];
$attachments[] = ['type' => 'Page',
'mediaType' => $attachment['mimetype'],
'url' => $attachment['url'],
'name' => $attachment['description']];
}*/
}
return $attachments;
@ -1543,7 +1550,7 @@ class Transmitter
$richbody = preg_replace_callback($regexp, ['self', 'mentionCallback'], $item['body']);
$richbody = BBCode::removeAttachment($richbody);
$data['contentMap'][$language] = BBCode::convert($richbody, false);
$data['contentMap'][$language] = BBCode::convert($richbody, false, BBCode::EXTERNAL);
}
$data['source'] = ['content' => $item['body'], 'mediaType' => "text/bbcode"];

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