X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FModel%2FItem.php;h=e54d5fb098e8a79f337814200cc728494f31282a;hb=f81192b4c3838480154301ac25cbbbed58593380;hp=5f83ae38dad2e6f030602c91735fc4e91ab14a59;hpb=f19cd54195b8d5395bc221734890ee654212dfe5;p=friendica.git diff --git a/src/Model/Item.php b/src/Model/Item.php index 5f83ae38da..e54d5fb098 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -117,7 +117,7 @@ class Item 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', @@ -138,7 +138,7 @@ class Item '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' @@ -330,7 +330,7 @@ class Item */ public static function markForDeletionById(int $item_id, int $priority = Worker::PRIORITY_HIGH): bool { - Logger::info('Mark item for deletion by id', ['id' => $item_id, 'callstack' => System::callstack()]); + Logger::info('Mark item for deletion by id', ['id' => $item_id]); // locate item to be deleted $fields = [ 'id', 'uri', 'uri-id', 'uid', 'parent', 'parent-uri-id', 'origin', @@ -795,7 +795,7 @@ class Item } if (!DBA::isResult($parent)) { - Logger::notice('item parent was not found - ignoring item', ['uri-id' => $item['uri-id'], 'thr-parent-id' => $item['thr-parent-id'], 'uid' => $item['uid'], 'callstack' => System::callstack(20)]); + Logger::notice('item parent was not found - ignoring item', ['uri-id' => $item['uri-id'], 'thr-parent-id' => $item['thr-parent-id'], 'uid' => $item['uid']]); return []; } @@ -880,6 +880,10 @@ class Item if (is_int($notify) && in_array($notify, Worker::PRIORITIES)) { $priority = $notify; } + + // Mastodon style API visibility + $copy_permissions = ($item['visibility'] ?? 'private') == 'private'; + unset($item['visibility']); } else { $item['network'] = trim(($item['network'] ?? '') ?: Protocol::PHANTOM); } @@ -1038,10 +1042,12 @@ class Item // Reshares have to keep their permissions to allow groups to work if (!$defined_permissions && (!$item['origin'] || ($item['verb'] != Activity::ANNOUNCE))) { - $item['allow_cid'] = $toplevel_parent['allow_cid']; - $item['allow_gid'] = $toplevel_parent['allow_gid']; - $item['deny_cid'] = $toplevel_parent['deny_cid']; - $item['deny_gid'] = $toplevel_parent['deny_gid']; + // Don't store the permissions on pure AP posts + $store_permissions = ($item['network'] != Protocol::ACTIVITYPUB) || $item['origin'] || !empty($item['diaspora_signed_text']); + $item['allow_cid'] = $store_permissions ? $toplevel_parent['allow_cid'] : ''; + $item['allow_gid'] = $store_permissions ? $toplevel_parent['allow_gid'] : ''; + $item['deny_cid'] = $store_permissions ? $toplevel_parent['deny_cid'] : ''; + $item['deny_gid'] = $store_permissions ? $toplevel_parent['deny_gid'] : ''; } $parent_origin = $toplevel_parent['origin']; @@ -1183,7 +1189,7 @@ class Item if (!empty($quote_id)) { // This is one of these "should not happen" situations. // The protocol implementations should already have done this job. - Logger::notice('Quote-uri-id detected in post', ['id' => $quote_id, 'guid' => $item['guid'], 'uri-id' => $item['uri-id'], 'callstack' => System::callstack(20)]); + Logger::notice('Quote-uri-id detected in post', ['id' => $quote_id, 'guid' => $item['guid'], 'uri-id' => $item['uri-id']]); $item['quote-uri-id'] = $quote_id; } } @@ -1359,6 +1365,9 @@ class Item 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'], $posted_item['parent-uri-id']); + } } else { Hook::callAll('post_remote_end', $posted_item); } @@ -1405,10 +1414,12 @@ class 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) { + ActivityPub\Transmitter::storeReceiversForItem($posted_item); + Worker::add(['priority' => $priority, 'dont_fork' => true], 'Notifier', $notify_type, (int)$posted_item['uri-id'], (int)$posted_item['uid']); } @@ -1475,6 +1486,10 @@ class Item */ 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']] @@ -1526,7 +1541,25 @@ class Item 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) { @@ -1981,19 +2014,31 @@ class Item * @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); @@ -2025,15 +2070,12 @@ class Item return []; } - $availableLanguages = DI::l10n()->getAvailableLanguages(true); - $availableLanguages = DI::l10n()->convertForLanguageDetection($availableLanguages); - - $ld = new Language(array_keys($availableLanguages)); + $ld = new Language(DI::l10n()->getDetectableLanguages()); $result = []; foreach (self::splitByBlocks($searchtext) as $block) { - $languages = $ld->detect($block)->limit(0, $count)->close() ?: []; + $languages = $ld->detect($block)->close() ?: []; $data = [ 'text' => $block, @@ -2048,10 +2090,32 @@ class Item } } + $result = self::compactLanguages($result); + arsort($result); - $result = array_slice($result, 0, $count); + return array_slice($result, 0, $count); + } - return $result; + /** + * Concert the language code in the detection result to ISO 639-1. + * On duplicates the system uses the higher quality value. + * + * @param array $result + * @return array + */ + private static function compactLanguages(array $result): array + { + $languages = []; + foreach ($result as $language => $quality) { + if ($quality == 0) { + continue; + } + $code = DI::l10n()->toISO6391($language); + if (empty($languages[$code]) || ($languages[$code] < $quality)) { + $languages[$code] = $quality; + } + } + return $languages; } /** @@ -2134,7 +2198,15 @@ class Item $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); + + $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; @@ -2162,12 +2234,12 @@ class Item $hostPart = $host ?: $parsed['host'] ?? ''; if (!$hostPart) { - Logger::warning('Empty host GUID part', ['uri' => $uri, 'host' => $host, 'parsed' => $parsed, 'callstack' => System::callstack(10)]); + Logger::warning('Empty host GUID part', ['uri' => $uri, 'host' => $host, 'parsed' => $parsed]); } // 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; } @@ -2512,11 +2584,14 @@ class Item $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; } @@ -3393,7 +3468,7 @@ class Item 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 @@ -3701,7 +3776,7 @@ class Item DI::profiler()->startRecording('rendering'); $trailing = ''; foreach ($PostMedias as $PostMedia) { - if (strpos($item['body'], $PostMedia->url)) { + if (strpos($item['body'], (string)$PostMedia->url)) { continue; } @@ -3748,16 +3823,16 @@ class Item 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 { @@ -3905,11 +3980,12 @@ class Item * Fetches item for given URI or plink * * @param string $uri - * @param integer $uid + * @param int $uid + * @param int $completion * * @return integer item id */ - public static function fetchByLink(string $uri, int $uid = 0): int + public static function fetchByLink(string $uri, int $uid = 0, int $completion = ActivityPub\Receiver::COMPLETION_MANUAL): int { Logger::info('Trying to fetch link', ['uid' => $uid, 'uri' => $uri]); $item_id = self::searchByLink($uri, $uid); @@ -3930,7 +4006,7 @@ class Item return is_numeric($hookData['item_id']) ? $hookData['item_id'] : 0; } - $fetched_uri = ActivityPub\Processor::fetchMissingActivity($uri, [], '', ActivityPub\Receiver::COMPLETION_MANUAL, $uid); + $fetched_uri = ActivityPub\Processor::fetchMissingActivity($uri, [], '', $completion, $uid); if ($fetched_uri) { $item_id = self::searchByLink($fetched_uri, $uid); @@ -3990,7 +4066,7 @@ class Item } $url = $shared['message_id'] ?: $shared['link']; - $id = self::fetchByLink($url); + $id = self::fetchByLink($url, 0, ActivityPub\Receiver::COMPLETION_ASYNC); if (!$id) { Logger::notice('Post could not be fetched.', ['url' => $url, 'uid' => $uid]); return 0;