<?php
/**
- * @copyright Copyright (C) 2010-2023, the Friendica project
+ * @copyright Copyright (C) 2010-2024, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
const DELIVER_FIELDLIST = [
'uid', 'id', 'parent', 'uri-id', 'uri', 'thr-parent', 'parent-uri', 'guid',
'parent-guid', 'conversation', 'received', 'created', 'edited', 'verb', 'object-type', 'object', 'target',
- 'private', 'title', 'body', 'raw-body', 'location', 'coord', 'app',
+ 'private', 'title', 'body', 'raw-body', 'language', 'location', 'coord', 'app',
'inform', 'deleted', 'extid', 'post-type', 'post-reason', 'gravity',
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid',
'author-id', 'author-addr', 'author-link', 'author-name', 'author-avatar', 'owner-id', 'owner-link', 'contact-uid',
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'post-type', 'post-reason',
'private', 'pubmail', 'visible', 'starred',
'unseen', 'deleted', 'origin', 'mention', 'global', 'network',
- 'title', 'content-warning', 'body', 'location', 'coord', 'app',
+ 'title', 'content-warning', 'body', 'language', 'location', 'coord', 'app',
'rendered-hash', 'rendered-html', 'object-type', 'object', 'target-type', 'target',
'author-id', 'author-link', 'author-name', 'author-avatar', 'author-network',
'owner-id', 'owner-link', 'owner-name', 'owner-avatar', 'causer-id'
$previous = Post::selectFirst(['edited'], $condition);
}
+ if (!empty($fields['body'])) {
+ $fields['body'] = self::setHashtags($fields['body']);
+ }
+
$rows = Post::update($fields, $condition);
if (is_bool($rows)) {
return $rows;
// locate item to be deleted
$fields = [
'id', 'uri', 'uri-id', 'uid', 'parent', 'parent-uri-id', 'origin',
- 'deleted', 'resource-id', 'event-id',
+ 'thr-parent-id', 'deleted', 'resource-id', 'event-id', 'vid', 'body',
'verb', 'object-type', 'object', 'target', 'contact-id', 'psid', 'gravity'
];
$item = Post::selectFirst($fields, ['id' => $item_id]);
DI::notify()->deleteForItem($item['uri-id']);
DI::notification()->deleteForItem($item['uri-id']);
+ if (in_array($item['gravity'], [self::GRAVITY_ACTIVITY, self::GRAVITY_COMMENT])) {
+ Post\Counts::update($item['thr-parent-id'], $item['parent-uri-id'], $item['vid'], $item['verb'], $item['body']);
+ }
+
Logger::info('Item has been marked for deletion.', ['id' => $item_id]);
return true;
if ($notify) {
DI::contentItem()->postProcessPost($posted_item);
if ($copy_permissions && ($posted_item['thr-parent-id'] != $posted_item['uri-id']) && ($posted_item['private'] == self::PRIVATE)) {
- DI::contentItem()->copyPermissions($posted_item['thr-parent-id'], $posted_item['uri-id']);
+ DI::contentItem()->copyPermissions($posted_item['thr-parent-id'], $posted_item['uri-id'], $posted_item['parent-uri-id']);
}
} else {
Hook::callAll('post_remote_end', $posted_item);
}
if (!empty($source) && ($transmit || DI::config()->get('debug', 'store_source'))) {
- Post\Activity::insert($item['uri-id'], $source);
+ Post\Activity::insert($posted_item['uri-id'], $source);
}
if ($transmit) {
- Worker::add(['priority' => $priority, 'dont_fork' => true], 'Notifier', $notify_type, (int)$posted_item['uri-id'], (int)$posted_item['uid']);
- }
+ ActivityPub\Transmitter::storeReceiversForItem($posted_item);
- // Fill the cache with the rendered content.
- if (in_array($posted_item['gravity'], [self::GRAVITY_PARENT, self::GRAVITY_COMMENT]) && ($posted_item['uid'] == 0)) {
- self::updateDisplayCache($posted_item['uri-id']);
+ Worker::add(['priority' => $priority, 'dont_fork' => true], 'Notifier', $notify_type, (int)$posted_item['uri-id'], (int)$posted_item['uid']);
}
if ($inserted) {
- Post\Engagement::storeFromItem($posted_item);
+ // Fill the cache with the rendered content.
+ if (in_array($posted_item['gravity'], [self::GRAVITY_PARENT, self::GRAVITY_COMMENT])) {
+ self::updateDisplayCache($posted_item['uri-id']);
+ }
+
+ if (in_array($posted_item['gravity'], [self::GRAVITY_ACTIVITY, self::GRAVITY_COMMENT])) {
+ Post\Counts::update($posted_item['thr-parent-id'], $posted_item['parent-uri-id'], $posted_item['vid'], $posted_item['verb'], $posted_item['body']);
+ }
+
+ $engagement_uri_id = Post\Engagement::storeFromItem($posted_item);
+ if ($engagement_uri_id) {
+ self::reshareChannelPost($engagement_uri_id);
+ }
}
return $post_user_id;
}
+ private static function reshareChannelPost(int $uri_id)
+ {
+ $item = Post::selectFirst(['id', 'private', 'network', 'language', 'owner-id'], ['uri-id' => $uri_id, 'uid' => 0]);
+ if (empty($item['id'])) {
+ return;
+ }
+
+ if (($item['private'] != self::PUBLIC) || !in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN])) {
+ return;
+ }
+
+ $engagement = DBA::selectFirst('post-engagement', ['searchtext', 'media-type'], ['uri-id' => $uri_id]);
+ if (empty($engagement['searchtext'])) {
+ return;
+ }
+
+ $language = !empty($item['language']) ? array_key_first(json_decode($item['language'], true)) : '';
+ $tags = array_column(Tag::getByURIId($uri_id, [Tag::HASHTAG]), 'name');
+
+ foreach (DI::userDefinedChannel()->getMatchingChannelUsers($engagement['searchtext'], $language, $tags, $engagement['media-type'], $item['owner-id']) as $uid) {
+ Logger::debug('Reshare post', ['uid' => $uid, 'uri-id' => $uri_id, 'language' => $language, 'tags' => $tags, 'searchtext' => $engagement['searchtext'], 'media_type' => $engagement['media-type']]);
+ self::performActivity($item['id'], 'announce', $uid);
+ }
+ }
+
/**
* Fetch the post reason for a given item array
*
*/
private static function setOwnerforResharedItem(array $item)
{
+ if ($item['uid'] == 0) {
+ return;
+ }
+
$parent = Post::selectFirst(
['id', 'causer-id', 'owner-id', 'author-id', 'author-link', 'origin', 'post-reason'],
['uri-id' => $item['thr-parent-id'], 'uid' => $item['uid']]
return;
}
+ $languages = $item['language'] ? array_keys(json_decode($item['language'], true)) : [];
+
foreach (Tag::getUIDListByURIId($item['uri-id']) as $uid => $tags) {
+ if (!empty($languages)) {
+ $keep = false;
+ $user_languages = User::getWantedLanguages($uid);
+ foreach ($user_languages as $language) {
+ if (in_array($language, $languages)) {
+ $keep = true;
+ }
+ }
+ if ($keep) {
+ Logger::debug('Wanted languages found', ['uid' => $uid, 'user-languages' => $user_languages, 'item-languages' => $languages]);
+ } else {
+ Logger::debug('No wanted languages found', ['uid' => $uid, 'user-languages' => $user_languages, 'item-languages' => $languages]);
+ continue;
+ }
+ }
+
$stored = self::storeForUserByUriId($item['uri-id'], $uid, ['post-reason' => self::PR_TAG]);
Logger::info('Stored item for users', ['uri-id' => $item['uri-id'], 'uid' => $uid, 'stored' => $stored]);
foreach ($tags as $tag) {
* @return string detected language
* @throws \Text_LanguageDetect_Exception
*/
- private static function getLanguage(array $item): string
+ private static function getLanguage(array $item): ?string
{
if (!empty($item['language'])) {
return $item['language'];
}
- if (!in_array($item['gravity'], [self::GRAVITY_PARENT, self::GRAVITY_COMMENT]) || empty($item['body'])) {
- return '';
+ $transmitted = [];
+ foreach ($item['transmitted-languages'] ?? [] as $language) {
+ $transmitted[$language] = 0;
}
- $languages = self::getLanguageArray($item['title'] . ' ' . ($item['content-warning'] ?? '') . ' ' . $item['body'], 3, $item['uri-id'], $item['author-id']);
+ $content = trim(($item['title'] ?? '') . ' ' . ($item['content-warning'] ?? '') . ' ' . ($item['body'] ?? ''));
+
+ if (!in_array($item['gravity'], [self::GRAVITY_PARENT, self::GRAVITY_COMMENT]) || empty($content)) {
+ return !empty($transmitted) ? json_encode($transmitted) : null;
+ }
+
+ $languages = self::getLanguageArray($content, 3, $item['uri-id'], $item['author-id']);
if (empty($languages)) {
- return '';
+ return !empty($transmitted) ? json_encode($transmitted) : null;
+ }
+
+ if (!empty($transmitted)) {
+ $languages = array_merge($transmitted, $languages);
+ arsort($languages);
}
return json_encode($languages);
}
if (empty($searchtext)) {
- return [];
+ return ['un' => 1];
}
$ld = new Language(DI::l10n()->getDetectableLanguages());
}
}
+ if (empty($result)) {
+ return ['un' => 1];
+ }
+
$result = self::compactLanguages($result);
arsort($result);
$used_languages = '';
foreach (json_decode($item['language'], true) as $language => $reliability) {
- $used_languages .= $iso639->nativeByCode1(substr($language, 0, 2)) . ' (' . $iso639->languageByCode1(substr($language, 0, 2)) . ' - ' . $language . "): " . number_format($reliability, 5) . '\n';
+ $code = DI::l10n()->toISO6391($language);
+
+ if ($code == 'un') {
+ $native = $language = DI::l10n()->t('Undetermined');
+ } else {
+ $native = $iso639->nativeByCode1($code);
+ $language = $iso639->languageByCode1($code);
+ }
+
+ if ($native != $language) {
+ $used_languages .= DI::l10n()->t('%s (%s - %s): %s', $native, $language, $code, number_format($reliability, 5)) . '\n';
+ } else {
+ $used_languages .= DI::l10n()->t('%s (%s): %s', $native, $code, number_format($reliability, 5)) . '\n';
+ }
}
$used_languages = DI::l10n()->t('Detected languages in this post:\n%s', $used_languages);
return $used_languages;
// Glue it together to be able to make a hash from it
if (!empty($parsed)) {
- $host_id = implode('/', $parsed);
+ $host_id = implode('/', (array)$parsed);
} else {
$host_id = $uri;
}
$result = self::insert($datarray2);
Logger::info('remote-self post original item', ['contact' => $contact['url'], 'result' => $result, 'item' => $datarray2]);
} else {
- $datarray['private'] = self::PUBLIC;
$datarray['app'] = 'Feed';
$result = true;
}
+ if ($result) {
+ unset($datarray['private']);
+ }
+
return (bool)$result;
}
unset($urlparts['fragment']);
try {
- $url = (string)Uri::fromParts($urlparts);
+ $url = (string)Uri::fromParts((array)$urlparts);
} catch (\InvalidArgumentException $e) {
DI::logger()->notice('Invalid URL', ['$url' => $url, '$urlparts' => $urlparts]);
/* See https://github.com/friendica/friendica/issues/12113
DI::profiler()->startRecording('rendering');
$trailing = '';
foreach ($PostMedias as $PostMedia) {
- if (strpos($item['body'], $PostMedia->url)) {
+ if (strpos($item['body'], (string)$PostMedia->url)) {
continue;
}
foreach ($options as $key => $option) {
if ($question['voters'] > 0) {
$percent = $option['replies'] / $question['voters'] * 100;
- $options[$key]['vote'] = DI::l10n()->tt('%2$s (%3$d%%, %1$d vote)', '%2$s (%3$d%%, %1$d votes)', $option['replies'], $option['name'], round($percent, 1));
+ $options[$key]['vote'] = DI::l10n()->tt('%2$s (%3$d%%, %1$d vote)', '%2$s (%3$d%%, %1$d votes)', $option['replies'] ?? 0, $option['name'], round($percent, 1));
} else {
- $options[$key]['vote'] = DI::l10n()->tt('%2$s (%1$d vote)', '%2$s (%1$d votes)', $option['replies'], $option['name']);
+ $options[$key]['vote'] = DI::l10n()->tt('%2$s (%1$d vote)', '%2$s (%1$d votes)', $option['replies'] ?? 0, $option['name']);
}
}
if (!empty($question['voters']) && !empty($question['endtime'])) {
- $summary = DI::l10n()->tt('%d voter. Poll end: %s', '%d voters. Poll end: %s', $question['voters'], Temporal::getRelativeDate($question['endtime']));
+ $summary = DI::l10n()->tt('%d voter. Poll end: %s', '%d voters. Poll end: %s', $question['voters'] ?? 0, Temporal::getRelativeDate($question['endtime']));
} elseif (!empty($question['voters'])) {
- $summary = DI::l10n()->tt('%d voter.', '%d voters.', $question['voters']);
+ $summary = DI::l10n()->tt('%d voter.', '%d voters.', $question['voters'] ?? 0);
} elseif (!empty($question['endtime'])) {
$summary = DI::l10n()->t('Poll end: %s', Temporal::getRelativeDate($question['endtime']));
} else {