mirror of
https://github.com/friendica/friendica
synced 2025-04-27 09:10:12 +00:00
Merge branch 'develop' into phpstan-level-1
This commit is contained in:
commit
8bbf9d25b6
561 changed files with 65016 additions and 61395 deletions
|
@ -51,7 +51,7 @@ class App
|
|||
{
|
||||
const PLATFORM = 'Friendica';
|
||||
const CODENAME = 'Yellow Archangel';
|
||||
const VERSION = '2024.09-dev';
|
||||
const VERSION = '2024.12-dev';
|
||||
|
||||
// Allow themes to control internal parameters
|
||||
// by changing App values in theme.php
|
||||
|
|
|
@ -273,6 +273,13 @@ class Page implements ArrayAccess
|
|||
'$max_imagesize' => round(Images::getMaxUploadBytes() / 1000000, 0),
|
||||
|
||||
]) . $this->page['htmlhead'];
|
||||
|
||||
if ($pConfig->get($localUID, 'accessibility', 'hide_empty_descriptions')) {
|
||||
$this->page['htmlhead'] .= "<style>.empty-description {display: none;}</style>\n";
|
||||
}
|
||||
if ($pConfig->get($localUID, 'accessibility', 'hide_custom_emojis')) {
|
||||
$this->page['htmlhead'] .= "<style>span.emoji.mastodon img {display: none;}</style>\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,15 +12,20 @@ use Friendica\Core\Protocol;
|
|||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Util\Strings;
|
||||
use GuzzleHttp\Psr7\Uri;
|
||||
|
||||
/**
|
||||
* ContactSelector class
|
||||
*/
|
||||
class ContactSelector
|
||||
{
|
||||
const SVG_DISABLED = -1;
|
||||
const SVG_COLOR_BLACK = 0;
|
||||
const SVG_BLACK = 1;
|
||||
const SVG_COLOR_WHITE = 2;
|
||||
const SVG_WHITE = 3;
|
||||
|
||||
static $serverdata = [];
|
||||
static $server_url = [];
|
||||
static $server_id = [];
|
||||
|
||||
/**
|
||||
* @param string $current current
|
||||
|
@ -50,50 +55,51 @@ class ContactSelector
|
|||
return $o;
|
||||
}
|
||||
|
||||
private static function getServerForProfile(string $profile)
|
||||
/**
|
||||
* Fetches the server id for a given profile
|
||||
*
|
||||
* @param string $profile
|
||||
* @return integer
|
||||
*/
|
||||
public static function getServerIdForProfile(string $profile): int
|
||||
{
|
||||
$server_url = self::getServerURLForProfile($profile);
|
||||
|
||||
if (!empty(self::$serverdata[$server_url])) {
|
||||
return self::$serverdata[$server_url];
|
||||
if (!empty(self::$server_id[$profile])) {
|
||||
return self::$server_id[$profile];
|
||||
}
|
||||
|
||||
// Now query the GServer for the platform name
|
||||
$gserver = DBA::selectFirst('gserver', ['platform', 'network'], ['nurl' => $server_url]);
|
||||
$contact = DBA::selectFirst('contact', ['gsid'], ['uid' => 0, 'nurl' => Strings::normaliseLink($profile)]);
|
||||
if (empty($contact['gsid'])) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
self::$serverdata[$server_url] = $gserver;
|
||||
return $gserver;
|
||||
self::$server_id[$profile] = $contact['gsid'];
|
||||
|
||||
return $contact['gsid'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $profile Profile URL
|
||||
* @return string Server URL
|
||||
* @throws \Exception
|
||||
* Get server array for a given server id
|
||||
*
|
||||
* @param integer $gsid
|
||||
* @return array
|
||||
*/
|
||||
private static function getServerURLForProfile(string $profile): string
|
||||
private static function getServerForId(int $gsid = null): array
|
||||
{
|
||||
if (!empty(self::$server_url[$profile])) {
|
||||
return self::$server_url[$profile];
|
||||
if (empty($gsid)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$server_url = '';
|
||||
|
||||
// Fetch the server url from the contact table
|
||||
$contact = DBA::selectFirst('contact', ['baseurl'], ['uid' => 0, 'nurl' => Strings::normaliseLink($profile)]);
|
||||
if (DBA::isResult($contact) && !empty($contact['baseurl'])) {
|
||||
$server_url = Strings::normaliseLink($contact['baseurl']);
|
||||
if (!empty(self::$serverdata[$gsid])) {
|
||||
return self::$serverdata[$gsid];
|
||||
}
|
||||
|
||||
if (empty($server_url)) {
|
||||
// Create the server url out of the profile url
|
||||
$parts = parse_url($profile);
|
||||
unset($parts['path']);
|
||||
$server_url = Strings::normaliseLink((string)Uri::fromParts((array)$parts));
|
||||
$gserver = DBA::selectFirst('gserver', ['id', 'url', 'platform', 'network'], ['id' => $gsid]);
|
||||
if (empty($gserver)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
self::$server_url[$profile] = $server_url;
|
||||
|
||||
return $server_url;
|
||||
self::$serverdata[$gserver['id']] = $gserver;
|
||||
return $gserver;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -106,7 +112,7 @@ class ContactSelector
|
|||
* @return string
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function networkToName(string $network, string $profile = '', string $protocol = '', int $gsid = null): string
|
||||
public static function networkToName(string $network, string $protocol = '', int $gsid = null): string
|
||||
{
|
||||
$nets = [
|
||||
Protocol::DFRN => DI::l10n()->t('DFRN'),
|
||||
|
@ -137,15 +143,8 @@ class ContactSelector
|
|||
|
||||
$networkname = str_replace($search, $replace, $network);
|
||||
|
||||
if ((in_array($network, Protocol::FEDERATED)) && ($profile != "")) {
|
||||
if (!empty($gsid) && !empty(self::$serverdata[$gsid])) {
|
||||
$gserver = self::$serverdata[$gsid];
|
||||
} elseif (!empty($gsid)) {
|
||||
$gserver = DBA::selectFirst('gserver', ['platform', 'network'], ['id' => $gsid]);
|
||||
self::$serverdata[$gsid] = $gserver;
|
||||
} else {
|
||||
$gserver = self::getServerForProfile($profile);
|
||||
}
|
||||
if (in_array($network, Protocol::FEDERATED) && !empty($gsid)) {
|
||||
$gserver = self::getServerForId($gsid);
|
||||
|
||||
if (!empty($gserver['platform'])) {
|
||||
$platform = $gserver['platform'];
|
||||
|
@ -155,80 +154,132 @@ class ContactSelector
|
|||
|
||||
if (!empty($platform)) {
|
||||
$networkname = $platform;
|
||||
|
||||
if ($network == Protocol::ACTIVITYPUB) {
|
||||
$networkname .= ' (AP)';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($protocol) && ($protocol != $network)) {
|
||||
if (!empty($protocol) && ($protocol != $network) && $network != Protocol::DFRN) {
|
||||
$networkname = DI::l10n()->t('%s (via %s)', $networkname, self::networkToName($protocol));
|
||||
} elseif (in_array($network, ['', $protocol]) && ($network == Protocol::DFRN)) {
|
||||
$networkname .= ' (DFRN)';
|
||||
} elseif (in_array($network, ['', $protocol]) && ($network == Protocol::DIASPORA) && ($platform != 'diaspora')) {
|
||||
$networkname .= ' (Diaspora)';
|
||||
}
|
||||
|
||||
return $networkname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines network's icon name
|
||||
* Fetch the platform SVG of a given system
|
||||
* @see https://codeberg.org/FediverseIconography/pages
|
||||
* @see https://github.com/simple-icons/simple-icons
|
||||
* @see https://icon-sets.iconify.design
|
||||
*
|
||||
* @param string $network network
|
||||
* @param string $profile optional, default empty
|
||||
* @param int $gsid Server id
|
||||
* @return string Name for network icon
|
||||
* @throws \Exception
|
||||
* @param string $network
|
||||
* @param integer|null $gsid
|
||||
* @param string $platform
|
||||
* @param integer $uid
|
||||
* @return string
|
||||
*/
|
||||
public static function networkToIcon(string $network, string $profile = "", int $gsid = null): string
|
||||
public static function networkToSVG(string $network, int $gsid = null, string $platform = '', int $uid = 0): string
|
||||
{
|
||||
$nets = [
|
||||
Protocol::DFRN => 'friendica',
|
||||
Protocol::OSTATUS => 'gnu-social', // There is no generic OStatus icon
|
||||
Protocol::FEED => 'rss',
|
||||
Protocol::MAIL => 'inbox',
|
||||
Protocol::DIASPORA => 'diaspora',
|
||||
Protocol::ZOT => 'hubzilla',
|
||||
Protocol::LINKEDIN => 'linkedin',
|
||||
Protocol::XMPP => 'xmpp',
|
||||
Protocol::MYSPACE => 'file-text-o', /// @todo
|
||||
Protocol::GPLUS => 'google-plus',
|
||||
Protocol::PUMPIO => 'file-text-o', /// @todo
|
||||
Protocol::TWITTER => 'twitter',
|
||||
Protocol::DISCOURSE => 'dot-circle-o', /// @todo
|
||||
Protocol::DIASPORA2 => 'diaspora',
|
||||
Protocol::STATUSNET => 'gnu-social',
|
||||
Protocol::ACTIVITYPUB => 'activitypub',
|
||||
Protocol::PNUT => 'file-text-o', /// @todo
|
||||
Protocol::TUMBLR => 'tumblr',
|
||||
Protocol::BLUESKY => 'circle', /// @todo
|
||||
];
|
||||
$platform_icon_style = $uid ? (DI::pConfig()->get($uid, 'accessibility', 'platform_icon_style') ?? self::SVG_COLOR_BLACK) : self::SVG_COLOR_BLACK;
|
||||
|
||||
$platform_icons = ['diaspora' => 'diaspora', 'friendica' => 'friendica', 'friendika' => 'friendica',
|
||||
'GNU Social' => 'gnu-social', 'gnusocial' => 'gnu-social', 'hubzilla' => 'hubzilla',
|
||||
'mastodon' => 'mastodon', 'hometown' => 'mastodon', 'peertube' => 'peertube', 'pixelfed' => 'pixelfed',
|
||||
'pleroma' => 'pleroma', 'akkoma' => 'pleroma', 'red' => 'hubzilla', 'redmatrix' => 'hubzilla',
|
||||
'socialhome' => 'social-home', 'wordpress' => 'wordpress', 'lemmy' => 'users',
|
||||
'plume' => 'plume', 'funkwhale' => 'funkwhale', 'nextcloud' => 'nextcloud', 'drupal' => 'drupal',
|
||||
'firefish' => 'fire', 'calckey' => 'calculator', 'kbin' => 'check', 'threads' => 'instagram'];
|
||||
if ($platform_icon_style == self::SVG_DISABLED) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$nets = [
|
||||
Protocol::ACTIVITYPUB => 'activitypub', // https://commons.wikimedia.org/wiki/File:ActivityPub-logo-symbol.svg
|
||||
Protocol::BLUESKY => 'bluesky', // https://commons.wikimedia.org/wiki/File:Bluesky_Logo.svg
|
||||
Protocol::DFRN => 'friendica',
|
||||
Protocol::DIASPORA => 'diaspora', // https://www.svgrepo.com/svg/362315/diaspora
|
||||
Protocol::DIASPORA2 => 'diaspora', // https://www.svgrepo.com/svg/362315/diaspora
|
||||
Protocol::DISCOURSE => 'discourse', // https://commons.wikimedia.org/wiki/File:Discourse_icon.svg
|
||||
Protocol::FEED => 'rss', // https://commons.wikimedia.org/wiki/File:Generic_Feed-icon.svg
|
||||
Protocol::MAIL => 'email', // https://www.svgrepo.com/svg/501173/email
|
||||
Protocol::OSTATUS => '',
|
||||
Protocol::PNUT => '',
|
||||
Protocol::PUMPIO => 'pump-io', // https://commons.wikimedia.org/wiki/File:Pump.io_Logo.svg
|
||||
Protocol::STATUSNET => '',
|
||||
Protocol::TUMBLR => 'tumblr', // https://commons.wikimedia.org/wiki/File:Tumblr.svg
|
||||
Protocol::TWITTER => '',
|
||||
Protocol::ZOT => 'hubzilla', // https://www.svgrepo.com/svg/362219/hubzilla
|
||||
];
|
||||
|
||||
$search = array_keys($nets);
|
||||
$replace = array_values($nets);
|
||||
|
||||
$network_icon = str_replace($search, $replace, $network);
|
||||
$network_svg = str_replace($search, $replace, $network);
|
||||
|
||||
if ((in_array($network, Protocol::FEDERATED)) && ($profile != "")) {
|
||||
if (!empty($gsid) && !empty(self::$serverdata[$gsid])) {
|
||||
$gserver = self::$serverdata[$gsid];
|
||||
} elseif (!empty($gsid)) {
|
||||
$gserver = DBA::selectFirst('gserver', ['platform', 'network'], ['id' => $gsid]);
|
||||
self::$serverdata[$gsid] = $gserver;
|
||||
} else {
|
||||
$gserver = self::getServerForProfile($profile);
|
||||
}
|
||||
if (!empty($gserver['platform'])) {
|
||||
$network_icon = $platform_icons[strtolower($gserver['platform'])] ?? $network_icon;
|
||||
}
|
||||
if (in_array($network, Protocol::FEDERATED) && !empty($gsid)) {
|
||||
$gserver = self::getServerForId($gsid);
|
||||
$platform = $gserver['platform'];
|
||||
}
|
||||
|
||||
return $network_icon;
|
||||
$svg = ['aardwolf', 'activitypods', 'activitypub', 'akkoma', 'anfora', 'awakari', 'azorius',
|
||||
'bluesky', 'bonfire', 'bookwyrm', 'bridgy_fed', 'brighteon_social', 'brutalinks', 'calckey',
|
||||
'castopod', 'catodon', 'chatter_net', 'chuckya', 'clubsall', 'communecter', 'decodon',
|
||||
'diaspora', 'discourse', 'dolphin', 'drupal', 'email', 'emissary', 'epicyon', 'f2ap',
|
||||
'fedibird', 'fedify', 'firefish', 'flipboard', 'flohmarkt', 'forgefriends', 'forgejo',
|
||||
'forte', 'foundkey', 'friendica', 'funkwhale', 'gancio', 'gath.io', 'ghost', 'gitlab',
|
||||
'glitch-soc', 'glitchsoc', 'gnu_social', 'gnusocial', 'goblin', 'go-fed', 'gotosocial',
|
||||
'greatape', 'guppe', 'hollo', 'hometown', 'honk', 'hubzilla', 'iceshrimp', 'juick', 'kazarma',
|
||||
'kbin', 'kepi', 'kitsune', 'kmyblue', 'kookie', 'ktistec', 'lemmy', 'loops', 'mastodon',
|
||||
'mbin', 'micro.blog', 'minds', 'misskey', 'mistpark', 'mitra', 'mobilizon', 'neodb',
|
||||
'newsmast', 'nextcloud_social', 'nodebb', 'osada', 'owncast', 'peertube', 'piefed', 'pinetta',
|
||||
'pixelfed', 'pleroma', 'plume', 'postmarks', 'prismo', 'pump-io', 'rebased', 'redmatrix',
|
||||
'reel2bits', 'rss', 'ruffy', 'sakura', 'seppo', 'shadowfacts', 'sharky', 'shuttlecraft',
|
||||
'smilodon', 'smithereen', 'snac', 'soapbox', 'socialhome', 'streams', 'sublinks', 'sutty',
|
||||
'takahē', 'takesama', 'threads', 'tumblr', 'vernissage', 'vervis', 'vidzy', 'vocata', 'wafrn',
|
||||
'wildebeest', 'wordpress', 'write.as', 'writefreely', 'wxwclub', 'xwiki', 'zap'];
|
||||
|
||||
if (in_array($platform_icon_style,[self::SVG_WHITE, self::SVG_COLOR_WHITE])) {
|
||||
$svg = ['activitypub', 'akkoma', 'andstatus', 'bluesky', 'bonfire', 'bookwyrm', 'bridgy_fed',
|
||||
'calckey', 'castopod', 'diaspora', 'discourse', 'dolphin', 'drupal', 'email', 'firefish',
|
||||
'flipboard', 'flohmarkt', 'forgejo', 'friendica', 'funkwhale', 'ghost', 'gitlab',
|
||||
'glitch-soc', 'gnusocial', 'gotosocial', 'guppe', 'hollo', 'hubzilla', 'iceshrimp', 'kbin',
|
||||
'lemmy', 'loforo', 'loops', 'mastodon', 'mbin', 'microblog', 'minds', 'misskey', 'mobilizon',
|
||||
'nextcloud', 'owncast', 'peertube', 'phanpy', 'pixelfed', 'pleroma', 'plume', 'rss', 'shark',
|
||||
'soapbox', 'socialhome', 'streams', 'takahē', 'threads', 'tumblr', 'wordpress', 'write.as',
|
||||
'writefreely', 'xwiki', 'zap'];
|
||||
}
|
||||
|
||||
if (!empty($platform)) {
|
||||
$aliases = [
|
||||
'brighteon' => 'brighteon_social',
|
||||
'bridgy-fed' => 'bridgy_fed',
|
||||
'friendika' => 'friendica',
|
||||
'gathio' => 'gath.io',
|
||||
'GNU Social' => 'gnu_social',
|
||||
'gnusocial' => 'gnu_social',
|
||||
'guppe groups' => 'guppe',
|
||||
'microblog' => 'micro.blog',
|
||||
'microblogpub' => 'micro.blog',
|
||||
'nextcloud' => 'nextcloud_social',
|
||||
'red' => 'redmatrix',
|
||||
'sharkey' => 'sharky',
|
||||
'sutty-distributed-press' => 'sutty',
|
||||
];
|
||||
|
||||
$platform = str_replace(array_keys($aliases), array_values($aliases), $platform);
|
||||
$network_svg = in_array($platform, $svg) ? $platform : $network_svg;
|
||||
}
|
||||
|
||||
if (empty($network_svg)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$color = ['aardwolf', 'activitypods', 'activitypub', 'akkoma', 'bluesky', 'chuckya', 'decodon',
|
||||
'discourse', 'fedify', 'firefish', 'flipboard', 'friendica', 'gitlab', 'gnusocial', 'kookie',
|
||||
'loops', 'mastodon', 'mbin', 'misskey', 'neodb', 'newsmast', 'nodebb', 'peertube', 'pixelfed',
|
||||
'pleroma', 'rss', 'sharky', 'tumblr', 'vervis', 'vocata', 'wordpress'];
|
||||
|
||||
if (in_array($platform_icon_style, [self::SVG_COLOR_BLACK, self::SVG_COLOR_WHITE]) && in_array($network_svg, $color)) {
|
||||
return 'images/platforms/color/' . $network_svg . '.svg';
|
||||
} elseif (in_array($platform_icon_style, [self::SVG_WHITE, self::SVG_COLOR_WHITE])) {
|
||||
return 'images/platforms/white/' . $network_svg . '.svg';
|
||||
} else {
|
||||
return 'images/platforms/black/' . $network_svg . '.svg';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1516,8 +1516,8 @@ class Conversation
|
|||
'uriid' => $item['uri-id'],
|
||||
'author_gsid' => $item['author-gsid'],
|
||||
'network' => $item['network'],
|
||||
'network_name' => ContactSelector::networkToName($item['author-network'], $item['author-link'], $item['network'], $item['author-gsid']),
|
||||
'network_icon' => ContactSelector::networkToIcon($item['network'], $item['author-link'], $item['author-gsid']),
|
||||
'network_name' => ContactSelector::networkToName($item['author-network'], $item['network'], $item['author-gsid']),
|
||||
'network_svg' => ContactSelector::networkToSVG($item['network'], $item['author-gsid'], '', $this->session->getLocalUserId()),
|
||||
'linktitle' => $this->l10n->t('View %s\'s profile @ %s', $profile_name, $item['author-link']),
|
||||
'profile_url' => $profile_link,
|
||||
'item_photo_menu_html' => $this->item->photoMenu($item, $formSecurityToken),
|
||||
|
|
|
@ -686,7 +686,7 @@ class BBCode
|
|||
// to the last element
|
||||
$newbody = str_replace(
|
||||
'[$#saved_image' . $cnt . '#$]',
|
||||
'<img src="' . self::proxyUrl($image, self::INTERNAL, $uriid) . '" alt="' . DI::l10n()->t('Image/photo') . '" />',
|
||||
'<img src="' . self::proxyUrl($image, self::INTERNAL, $uriid) . '" alt="" class="empty-description"/>',
|
||||
$newbody
|
||||
);
|
||||
$cnt++;
|
||||
|
@ -849,6 +849,7 @@ class BBCode
|
|||
$img_str .= ' ' . $key . '="' . htmlspecialchars($value, ENT_COMPAT) . '"';
|
||||
}
|
||||
}
|
||||
$img_str .= ' ' . empty($attributes['alt']) ? 'class="empty-description"' : 'class="has-alt-description"';
|
||||
return $img_str . '>';
|
||||
},
|
||||
$text
|
||||
|
@ -920,6 +921,7 @@ class BBCode
|
|||
$contact = Contact::getByURL($attributes['profile'], false, ['network']);
|
||||
$network = $contact['network'] ?? Protocol::PHANTOM;
|
||||
|
||||
$gsid = ContactSelector::getServerIdForProfile($attributes['profile']);
|
||||
$tpl = Renderer::getMarkupTemplate('shared_content.tpl');
|
||||
$text .= self::SHARED_ANCHOR . Renderer::replaceMacros($tpl, [
|
||||
'$profile' => $attributes['profile'],
|
||||
|
@ -929,8 +931,8 @@ class BBCode
|
|||
'$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']),
|
||||
'$network_name' => ContactSelector::networkToName($network, '', $gsid),
|
||||
'$network_svg' => ContactSelector::networkToSVG($network, $gsid),
|
||||
'$content' => self::TOP_ANCHOR . self::setMentions(trim($content), 0, $network) . self::BOTTOM_ANCHOR,
|
||||
]);
|
||||
break;
|
||||
|
@ -1826,8 +1828,8 @@ class BBCode
|
|||
$text
|
||||
);
|
||||
|
||||
$text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '<img src="$3" style="width: $1px;" >', $text);
|
||||
$text = preg_replace("/\[zmg\=([0-9]*)x([0-9]*)\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$3" style="width: $1px;" >', $text);
|
||||
$text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '<img src="$3" style="width: $1px;" alt="" class="empty-description">', $text);
|
||||
$text = preg_replace("/\[zmg\=([0-9]*)x([0-9]*)\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$3" style="width: $1px;" alt="" class="empty-description">', $text);
|
||||
|
||||
$text = preg_replace_callback(
|
||||
"/\[[iz]mg\=(.*?)\](.*?)\[\/[iz]mg\]/ism",
|
||||
|
@ -1836,7 +1838,7 @@ class BBCode
|
|||
$alt = htmlspecialchars($matches[2], ENT_COMPAT);
|
||||
// Fix for Markdown problems with Diaspora, see issue #12701
|
||||
if (($simple_html != self::DIASPORA) || strpos($matches[2], '"') === false) {
|
||||
return '<img src="' . $matches[1] . '" alt="' . $alt . '" title="' . $alt . '">';
|
||||
return '<img src="' . $matches[1] . '" alt="' . $alt . '" title="' . $alt . '" class="' . (empty($alt) ? 'empty-description' : 'has-alt-description') . '">';
|
||||
} else {
|
||||
return '<img src="' . $matches[1] . '" alt="' . $alt . '">';
|
||||
}
|
||||
|
@ -1859,8 +1861,8 @@ class BBCode
|
|||
$text
|
||||
);
|
||||
|
||||
$text = preg_replace("/\[img\](.*?)\[\/img\]/ism", '<img src="$1" alt="' . DI::l10n()->t('Image/photo') . '" />', $text);
|
||||
$text = preg_replace("/\[zmg\](.*?)\[\/zmg\]/ism", '<img src="$1" alt="' . DI::l10n()->t('Image/photo') . '" />', $text);
|
||||
$text = preg_replace("/\[img\](.*?)\[\/img\]/ism", '<img src="$1" alt="" class="empty-description"/>', $text);
|
||||
$text = preg_replace("/\[zmg\](.*?)\[\/zmg\]/ism", '<img src="$1" alt="" class="empty-description" />', $text);
|
||||
|
||||
$text = self::convertImages($text, $simple_html, $uriid);
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ use Friendica\DI;
|
|||
use Friendica\Model\Photo;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Util\Network;
|
||||
use IntlChar;
|
||||
|
||||
class Plaintext
|
||||
{
|
||||
|
@ -237,39 +238,62 @@ class Plaintext
|
|||
*/
|
||||
private static function getParts(string $message, int $baselimit): array
|
||||
{
|
||||
$parts = [];
|
||||
$part = '';
|
||||
$parts = [];
|
||||
$part = '';
|
||||
$break_pos = 0;
|
||||
$comma_pos = 0;
|
||||
|
||||
$limit = $baselimit;
|
||||
|
||||
while ($message) {
|
||||
$pos1 = strpos($message, ' ');
|
||||
$pos2 = strpos($message, "\n");
|
||||
$pos_word = mb_strpos($message, ' ');
|
||||
$pos_paragraph = mb_strpos($message, "\n");
|
||||
|
||||
if (($pos1 !== false) && ($pos2 !== false)) {
|
||||
$pos = min($pos1, $pos2) + 1;
|
||||
} elseif ($pos1 !== false) {
|
||||
$pos = $pos1 + 1;
|
||||
} elseif ($pos2 !== false) {
|
||||
$pos = $pos2 + 1;
|
||||
if (($pos_word !== false) && ($pos_paragraph !== false)) {
|
||||
$pos = min($pos_word, $pos_paragraph) + 1;
|
||||
} elseif ($pos_word !== false) {
|
||||
$pos = $pos_word + 1;
|
||||
} elseif ($pos_paragraph !== false) {
|
||||
$pos = $pos_paragraph + 1;
|
||||
} else {
|
||||
$word = $message;
|
||||
$message = '';
|
||||
}
|
||||
|
||||
if (trim($message)) {
|
||||
$word = substr($message, 0, $pos);
|
||||
$message = trim(substr($message, $pos));
|
||||
$word = mb_substr($message, 0, $pos);
|
||||
$message = trim(mb_substr($message, $pos));
|
||||
}
|
||||
|
||||
if (Network::isValidHttpUrl(trim($word))) {
|
||||
$limit += mb_strlen(trim($word)) - self::URL_LENGTH;
|
||||
}
|
||||
|
||||
$break = mb_strrpos($word, "\n") !== false;
|
||||
if (!$break && (mb_strrpos($word, '. ') !== false || mb_strrpos($word, '? ') !== false || mb_strrpos($word, '! ') !== false)) {
|
||||
$break = IntlChar::isupper(mb_substr($message, 0, 1));
|
||||
}
|
||||
|
||||
$comma = (mb_strrpos($word, ', ') !== false) && IntlChar::isalpha(mb_substr($message, 0, 1));
|
||||
|
||||
if ((mb_strlen($part . $word) > $limit - 8) && ($parts || (mb_strlen($part . $word . $message) > $limit))) {
|
||||
$parts[] = trim($part);
|
||||
$part = '';
|
||||
$limit = $baselimit;
|
||||
if ($break_pos) {
|
||||
$parts[] = trim(mb_substr($part, 0, $break_pos));
|
||||
$part = mb_substr($part, $break_pos);
|
||||
} elseif ($comma_pos) {
|
||||
$parts[] = trim(mb_substr($part, 0, $comma_pos));
|
||||
$part = mb_substr($part, $comma_pos);
|
||||
} else {
|
||||
$parts[] = trim($part);
|
||||
$part = '';
|
||||
}
|
||||
$limit = $baselimit;
|
||||
$break_pos = 0;
|
||||
$comma_pos = 0;
|
||||
} elseif ($break) {
|
||||
$break_pos = $pos + mb_strlen($part);
|
||||
} elseif ($comma) {
|
||||
$comma_pos = $pos + mb_strlen($part);
|
||||
}
|
||||
$part .= $word;
|
||||
}
|
||||
|
|
|
@ -91,23 +91,59 @@ class Widget
|
|||
$networks = [Protocol::PHANTOM, Protocol::FACEBOOK, Protocol::APPNET, Protocol::TWITTER, Protocol::ZOT, Protocol::OSTATUS, Protocol::STATUSNET];
|
||||
Addon::loadAddons();
|
||||
|
||||
if (!Addon::isEnabled("discourse")) {
|
||||
if (!Addon::isEnabled('discourse')) {
|
||||
$networks[] = Protocol::DISCOURSE;
|
||||
}
|
||||
|
||||
if (!Addon::isEnabled("pumpio")) {
|
||||
if (!Addon::isEnabled('pumpio')) {
|
||||
$networks[] = Protocol::PUMPIO;
|
||||
}
|
||||
|
||||
if (!Addon::isEnabled("tumblr")) {
|
||||
if (!Addon::isEnabled('tumblr')) {
|
||||
$networks[] = Protocol::TUMBLR;
|
||||
}
|
||||
|
||||
if (!DI::config()->get("system", "diaspora_enabled")) {
|
||||
if (!DI::config()->get('system', 'diaspora_enabled')) {
|
||||
$networks[] = Protocol::DIASPORA;
|
||||
}
|
||||
|
||||
if (!Addon::isEnabled("pnut")) {
|
||||
if (!Addon::isEnabled('pnut')) {
|
||||
$networks[] = Protocol::PNUT;
|
||||
}
|
||||
return $networks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return available networks as array
|
||||
*
|
||||
* @return array Supported networks
|
||||
*/
|
||||
public static function availableNetworks(): array
|
||||
{
|
||||
$networks = [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::FEED];
|
||||
Addon::loadAddons();
|
||||
|
||||
if (Addon::isEnabled('discourse')) {
|
||||
$networks[] = Protocol::DISCOURSE;
|
||||
}
|
||||
|
||||
if (Addon::isEnabled('pumpio')) {
|
||||
$networks[] = Protocol::PUMPIO;
|
||||
}
|
||||
|
||||
if (Addon::isEnabled('tumblr')) {
|
||||
$networks[] = Protocol::TUMBLR;
|
||||
}
|
||||
|
||||
if (DI::config()->get('system', 'diaspora_enabled')) {
|
||||
$networks[] = Protocol::DIASPORA;
|
||||
}
|
||||
|
||||
if (function_exists('imap_open') && !DI::config()->get('system', 'imap_disabled')) {
|
||||
$networks[] = Protocol::MAIL;
|
||||
}
|
||||
|
||||
if (Addon::isEnabled('pnut')) {
|
||||
$networks[] = Protocol::PNUT;
|
||||
}
|
||||
return $networks;
|
||||
|
@ -382,7 +418,7 @@ class Widget
|
|||
|
||||
$tpl = Renderer::getMarkupTemplate('widget/remote_friends_common.tpl');
|
||||
return Renderer::replaceMacros($tpl, [
|
||||
'$desc' => DI::l10n()->tt("%d contact in common", "%d contacts in common", $total),
|
||||
'$desc' => DI::l10n()->tt('%d contact in common', '%d contacts in common', $total),
|
||||
'$base' => DI::baseUrl(),
|
||||
'$nickname' => $nickname,
|
||||
'$linkmore' => $total > 5 ? 'true' : '',
|
||||
|
@ -576,4 +612,4 @@ class Widget
|
|||
$channelname
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,10 +41,10 @@ class VCard
|
|||
|
||||
if ($contact['network'] != '') {
|
||||
$network_link = Strings::formatNetworkName($contact['network'], $contact_url);
|
||||
$network_avatar = ContactSelector::networkToIcon($contact['network'], $contact_url);
|
||||
$network_svg = ContactSelector::networkToSVG($contact['network'], $contact['gsid'], '', DI::userSession()->getLocalUserId());
|
||||
} else {
|
||||
$network_link = '';
|
||||
$network_avatar = '';
|
||||
$network_svg = '';
|
||||
}
|
||||
|
||||
$follow_link = '';
|
||||
|
@ -57,6 +57,11 @@ class VCard
|
|||
$photo = Contact::getPhoto($contact);
|
||||
|
||||
if (DI::userSession()->getLocalUserId()) {
|
||||
if (Contact\User::isIsBlocked($contact['id'], DI::userSession()->getLocalUserId())) {
|
||||
$hide_follow = true;
|
||||
$hide_mention = true;
|
||||
}
|
||||
|
||||
if ($contact['uid']) {
|
||||
$id = $contact['id'];
|
||||
$rel = $contact['rel'];
|
||||
|
@ -77,7 +82,7 @@ class VCard
|
|||
if (in_array($rel, [Contact::SHARING, Contact::FRIEND])) {
|
||||
$unfollow_link = 'contact/unfollow?url=' . urlencode($contact_url) . '&auto=1';
|
||||
} elseif (!$pending) {
|
||||
$follow_link = 'contact/follow?url=' . urlencode($contact_url) . '&auto=1';
|
||||
$follow_link = 'contact/follow?binurl=' . bin2hex($contact_url) . '&auto=1';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,7 +111,7 @@ class VCard
|
|||
'$matrix' => DI::l10n()->t('Matrix:'),
|
||||
'$location' => DI::l10n()->t('Location:'),
|
||||
'$network_link' => $network_link,
|
||||
'$network_avatar' => $network_avatar,
|
||||
'$network_svg' => $network_svg,
|
||||
'$network' => DI::l10n()->t('Network:'),
|
||||
'$account_type' => Contact::getAccountType($contact['contact-type']),
|
||||
'$follow' => DI::l10n()->t('Follow'),
|
||||
|
|
|
@ -11,6 +11,7 @@ use Friendica\Database\DBA;
|
|||
use Friendica\Model\User;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Protocol\ActivityPub\Transmitter;
|
||||
use Friendica\Protocol\Diaspora;
|
||||
|
||||
/**
|
||||
|
@ -32,26 +33,26 @@ class Protocol
|
|||
const SUPPORT_PRIVATE = [self::DFRN, self::DIASPORA, self::MAIL, self::ACTIVITYPUB, self::PUMPIO];
|
||||
|
||||
// Supported through a connector
|
||||
const DIASPORA2 = 'dspc'; // Diaspora connector
|
||||
const PUMPIO = 'pump'; // pump.io
|
||||
const TWITTER = 'twit'; // Twitter
|
||||
const DISCOURSE = 'dscs'; // Discourse
|
||||
const TUMBLR = 'tmbl'; // Tumblr
|
||||
const BLUESKY = 'bsky'; // Bluesky
|
||||
const DIASPORA2 = 'dspc'; // Diaspora connector
|
||||
const DISCOURSE = 'dscs'; // Discourse
|
||||
const PNUT = 'pnut'; // pnut.io
|
||||
const PUMPIO = 'pump'; // pump.io
|
||||
const TUMBLR = 'tmbl'; // Tumblr
|
||||
const TWITTER = 'twit'; // Twitter
|
||||
|
||||
// Dead protocols
|
||||
const OSTATUS = 'stat'; // GNU Social and other OStatus implementations
|
||||
const STATUSNET = 'stac'; // Statusnet connector
|
||||
const APPNET = 'apdn'; // app.net - Dead protocol
|
||||
const FACEBOOK = 'face'; // Facebook API - Not working anymore, API is closed
|
||||
const GPLUS = 'goog'; // Google+ - Dead in 2019
|
||||
const OSTATUS = 'stat'; // GNU Social and other OStatus implementations
|
||||
const STATUSNET = 'stac'; // Statusnet connector
|
||||
|
||||
// Currently unsupported
|
||||
const ICALENDAR = 'ical'; // iCalendar
|
||||
const MYSPACE = 'mysp'; // MySpace
|
||||
const LINKEDIN = 'lnkd'; // LinkedIn
|
||||
const MYSPACE = 'mysp'; // MySpace
|
||||
const NEWS = 'nntp'; // Network News Transfer Protocol
|
||||
const PNUT = 'pnut'; // pnut.io
|
||||
const XMPP = 'xmpp'; // XMPP
|
||||
const ZOT = 'zot!'; // Zot!
|
||||
|
||||
|
@ -211,7 +212,7 @@ class Protocol
|
|||
}
|
||||
|
||||
/**
|
||||
* Send a block message to a remote server. Only useful for connector addons.
|
||||
* Send a block message to a remote server.
|
||||
*
|
||||
* @param array $contact Public contact record to block
|
||||
* @param int $uid User issuing the block
|
||||
|
@ -220,6 +221,23 @@ class Protocol
|
|||
*/
|
||||
public static function block(array $contact, int $uid): ?bool
|
||||
{
|
||||
if (empty($contact['network'])) {
|
||||
throw new \InvalidArgumentException('Missing network key in contact array');
|
||||
}
|
||||
|
||||
$protocol = $contact['network'];
|
||||
if ($protocol == self::DFRN && !empty($contact['protocol'])) {
|
||||
$protocol = $contact['protocol'];
|
||||
}
|
||||
|
||||
if ($protocol == self::ACTIVITYPUB) {
|
||||
$activity_id = Transmitter::activityIDFromContact($contact['id'], $uid);
|
||||
if (empty($activity_id)) {
|
||||
return false;
|
||||
}
|
||||
return ActivityPub\Transmitter::sendActivity('Block', $contact['url'], $uid, $activity_id);
|
||||
}
|
||||
|
||||
// Catch-all hook for connector addons
|
||||
$hook_data = [
|
||||
'contact' => $contact,
|
||||
|
@ -232,7 +250,7 @@ class Protocol
|
|||
}
|
||||
|
||||
/**
|
||||
* Send an unblock message to a remote server. Only useful for connector addons.
|
||||
* Send an unblock message to a remote server.
|
||||
*
|
||||
* @param array $contact Public contact record to unblock
|
||||
* @param int $uid User revoking the block
|
||||
|
@ -241,6 +259,24 @@ class Protocol
|
|||
*/
|
||||
public static function unblock(array $contact, int $uid): ?bool
|
||||
{
|
||||
$owner = User::getOwnerDataById($uid);
|
||||
if (!DBA::isResult($owner)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($contact['network'])) {
|
||||
throw new \InvalidArgumentException('Missing network key in contact array');
|
||||
}
|
||||
|
||||
$protocol = $contact['network'];
|
||||
if ($protocol == self::DFRN && !empty($contact['protocol'])) {
|
||||
$protocol = $contact['protocol'];
|
||||
}
|
||||
|
||||
if ($protocol == self::ACTIVITYPUB) {
|
||||
return ActivityPub\Transmitter::sendContactUnblock($contact['url'], $contact['id'], $owner);
|
||||
}
|
||||
|
||||
// Catch-all hook for connector addons
|
||||
$hook_data = [
|
||||
'contact' => $contact,
|
||||
|
|
|
@ -24,6 +24,44 @@ class Update
|
|||
|
||||
const NEW_TABLE_STRUCTURE_VERSION = 1288;
|
||||
|
||||
/**
|
||||
* Returns the status of the current update
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getStatus(): int
|
||||
{
|
||||
return (int)DI::config()->get('system', 'update') ?? static::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the latest Version of the Friendica git repository and null, if this node doesn't check updates automatically
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getAvailableVersion(): ?string
|
||||
{
|
||||
return DI::keyValue()->get('git_friendica_version') ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true, if there's a new update and null if this node doesn't check updates automatically
|
||||
*
|
||||
* @return bool|null
|
||||
*/
|
||||
public static function isAvailable(): ?bool
|
||||
{
|
||||
if (DI::config()->get('system', 'check_new_version_url', 'none') != 'none') {
|
||||
if (version_compare(App::VERSION, static::getAvailableVersion()) < 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to check if the Database structure needs an update.
|
||||
*
|
||||
|
|
|
@ -188,6 +188,16 @@ class DBStructure
|
|||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current status of the Update
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getUpdateStatus(): int
|
||||
{
|
||||
return (int)DI::config()->get('system', 'dbupdate') ?? static::UPDATE_NOT_CHECKED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates DB structure from the installation and returns eventual errors messages
|
||||
*
|
||||
|
|
|
@ -10,8 +10,10 @@ namespace Friendica\Factory\Api\Mastodon;
|
|||
use Friendica\App\BaseURL;
|
||||
use Friendica\BaseFactory;
|
||||
use Friendica\Collection\Api\Mastodon\Fields;
|
||||
use Friendica\Content\Widget;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Profile\ProfileField\Repository\ProfileField as ProfileFieldRepository;
|
||||
use ImagickException;
|
||||
|
@ -75,10 +77,15 @@ class Account extends BaseFactory
|
|||
$fields = new Fields();
|
||||
|
||||
if (Contact::isLocal($account['url'])) {
|
||||
$self_contact = Contact::selectFirst(['uid'], ['nurl' => $account['nurl'], 'self' => true]);
|
||||
if (!empty($self_contact['uid'])) {
|
||||
$profileFields = $this->profileFieldRepo->selectPublicFieldsByUserId($self_contact['uid']);
|
||||
$profile_uid = User::getIdForContactId($account['id']);
|
||||
if ($profile_uid) {
|
||||
$profileFields = $this->profileFieldRepo->selectPublicFieldsByUserId($profile_uid);
|
||||
$fields = $this->mstdnFieldFactory->createFromProfileFields($profileFields);
|
||||
|
||||
if ($profile_uid == $uid) {
|
||||
$account['ap-followers_count'] = $this->getContactRelationCountForUid($uid, [Contact::FOLLOWER, Contact::FRIEND]);
|
||||
$account['ap-following_count'] = $this->getContactRelationCountForUid($uid, [Contact::SHARING, Contact::FRIEND]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,4 +105,20 @@ class Account extends BaseFactory
|
|||
|
||||
return new \Friendica\Object\Api\Mastodon\Account($this->baseUrl, $account, $fields);
|
||||
}
|
||||
|
||||
private function getContactRelationCountForUid(int $uid, array $rel): int
|
||||
{
|
||||
$condition = [
|
||||
'uid' => $uid,
|
||||
'rel' => $rel,
|
||||
'self' => false,
|
||||
'deleted' => false,
|
||||
'archive' => false,
|
||||
'pending' => false,
|
||||
'blocked' => false,
|
||||
'network' => Widget::availableNetworks(),
|
||||
];
|
||||
|
||||
return DBA::count('contact', $condition);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -216,7 +216,7 @@ class Status extends BaseFactory
|
|||
}
|
||||
|
||||
if ($platform == '') {
|
||||
$platform = ContactSelector::networkToName($item['network'], $item['author-link'], $item['network'], $item['author-gsid']);
|
||||
$platform = ContactSelector::networkToName($item['network'], $item['network'], $item['author-gsid']);
|
||||
}
|
||||
|
||||
$application = new \Friendica\Object\Api\Mastodon\Application($item['app'] ?: $platform);
|
||||
|
@ -328,7 +328,7 @@ class Status extends BaseFactory
|
|||
|
||||
$delivery_data = $uid != $item['uid'] ? null : new FriendicaDeliveryData($item['delivery_queue_count'], $item['delivery_queue_done'], $item['delivery_queue_failed']);
|
||||
$visibility_data = $uid != $item['uid'] ? null : new FriendicaVisibility($this->aclFormatter->expand($item['allow_cid']), $this->aclFormatter->expand($item['deny_cid']), $this->aclFormatter->expand($item['allow_gid']), $this->aclFormatter->expand($item['deny_gid']));
|
||||
$friendica = new FriendicaExtension($item['title'] ?? '', $item['changed'], $item['commented'], $item['received'], $counts->dislikes, $origin_dislike, $network, $platform, $version, $sitename, $delivery_data, $visibility_data);
|
||||
$friendica = new FriendicaExtension($item['title'] ?? '', $item['changed'], $item['commented'], $item['received'], $counts->dislikes, $origin_dislike, $network, $platform, $version, $sitename, $delivery_data, $visibility_data, BBCode::convertForUriId($item['uri-id'], $item['body'], BBCode::EXTERNAL));
|
||||
|
||||
return new \Friendica\Object\Api\Mastodon\Status($item, $account, $counts, $userAttributes, $sensitive, $application, $mentions, $tags, $card, $attachments, $in_reply, $reshare, $friendica, $quote, $poll, $emojis);
|
||||
}
|
||||
|
@ -399,7 +399,7 @@ class Status extends BaseFactory
|
|||
$attachments = [];
|
||||
$in_reply = [];
|
||||
$reshare = [];
|
||||
$friendica = new FriendicaExtension('', null, null, null, 0, false, null, null, null, null, null, null);
|
||||
$friendica = new FriendicaExtension('', null, null, null, 0, false, null, null, null, null, null, null, BBCode::convertForUriId($item['uri-id'], $item['body'], BBCode::EXTERNAL));
|
||||
|
||||
return new \Friendica\Object\Api\Mastodon\Status($item, $account, $counts, $userAttributes, $sensitive, $application, $mentions, $tags, $card, $attachments, $in_reply, $reshare, $friendica);
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ class Status extends BaseFactory
|
|||
*/
|
||||
public function createFromItemId(int $id, int $uid, bool $include_entities = false): \Friendica\Object\Api\Twitter\Status
|
||||
{
|
||||
$fields = ['parent-uri-id', 'uri-id', 'uid', 'author-id', 'author-link', 'author-network', 'owner-id', 'causer-id',
|
||||
$fields = ['parent-uri-id', 'uri-id', 'uid', 'author-id', 'author-link', 'author-network', 'author-gsid', 'owner-id', 'causer-id',
|
||||
'starred', 'app', 'title', 'body', 'raw-body', 'created', 'network','post-reason', 'language', 'gravity',
|
||||
'thr-parent-id', 'parent-author-id', 'parent-author-nick', 'uri', 'plink', 'private', 'vid', 'coord', 'quote-uri-id'];
|
||||
$item = Post::selectFirst($fields, ['id' => $id], ['order' => ['uid' => true]]);
|
||||
|
@ -89,7 +89,7 @@ class Status extends BaseFactory
|
|||
*/
|
||||
public function createFromUriId(int $uriId, int $uid = 0, bool $include_entities = false): \Friendica\Object\Api\Twitter\Status
|
||||
{
|
||||
$fields = ['parent-uri-id', 'uri-id', 'uid', 'author-id', 'author-link', 'author-network', 'owner-id', 'causer-id',
|
||||
$fields = ['parent-uri-id', 'uri-id', 'uid', 'author-id', 'author-link', 'author-network', 'author-gsid', 'owner-id', 'causer-id',
|
||||
'starred', 'app', 'title', 'body', 'raw-body', 'created', 'network','post-reason', 'language', 'gravity',
|
||||
'thr-parent-id', 'parent-author-id', 'parent-author-nick', 'uri', 'plink', 'private', 'vid', 'coord'];
|
||||
$item = Post::selectFirst($fields, ['uri-id' => $uriId, 'uid' => [0, $uid]], ['order' => ['uid' => true]]);
|
||||
|
|
|
@ -1082,17 +1082,17 @@ class Contact
|
|||
*/
|
||||
public static function markForArchival(array $contact)
|
||||
{
|
||||
if (!isset($contact['url']) && !empty($contact['id'])) {
|
||||
$fields = ['id', 'url', 'archive', 'self', 'term-date'];
|
||||
if ((!isset($contact['uri-id']) || !isset($contact['url']) || !isset($contact['archive']) || !isset($contact['self']) || !isset($contact['term-date'])) && !empty($contact['id'])) {
|
||||
$fields = ['id', 'uri-id', 'url', 'archive', 'self', 'term-date'];
|
||||
$contact = DBA::selectFirst('contact', $fields, ['id' => $contact['id']]);
|
||||
if (!DBA::isResult($contact)) {
|
||||
return;
|
||||
}
|
||||
} elseif (!isset($contact['url'])) {
|
||||
} elseif (!isset($contact['url']) || !isset($contact['uri-id'])) {
|
||||
Logger::info('Empty contact', ['contact' => $contact]);
|
||||
}
|
||||
|
||||
Logger::info('Contact is marked for archival', ['id' => $contact['id'], 'term-date' => $contact['term-date']]);
|
||||
Logger::info('Contact is marked for archival', ['id' => $contact['id'], 'archive' => $contact['archive'], 'term-date' => $contact['term-date'], 'url' => $contact['url']]);
|
||||
|
||||
// Contact already archived or "self" contact? => nothing to do
|
||||
if ($contact['archive'] || $contact['self']) {
|
||||
|
@ -1100,8 +1100,7 @@ class Contact
|
|||
}
|
||||
|
||||
if ($contact['term-date'] <= DBA::NULL_DATETIME) {
|
||||
self::update(['term-date' => DateTimeFormat::utcNow()], ['id' => $contact['id']]);
|
||||
self::update(['term-date' => DateTimeFormat::utcNow()], ['`nurl` = ? AND `term-date` <= ? AND NOT `self`', Strings::normaliseLink($contact['url']), DBA::NULL_DATETIME]);
|
||||
self::update(['term-date' => DateTimeFormat::utcNow()], ['uri-id' => $contact['uri-id'], 'self' => false]);
|
||||
} else {
|
||||
/* @todo
|
||||
* We really should send a notification to the owner after 2-3 weeks
|
||||
|
@ -1118,8 +1117,7 @@ class Contact
|
|||
* delete, though if the owner tries to unarchive them we'll start
|
||||
* the whole process over again.
|
||||
*/
|
||||
self::update(['archive' => true], ['id' => $contact['id']]);
|
||||
self::update(['archive' => true], ['nurl' => Strings::normaliseLink($contact['url']), 'self' => false]);
|
||||
self::update(['archive' => true], ['uri-id' => $contact['uri-id'], 'self' => false]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1144,28 +1142,25 @@ class Contact
|
|||
}
|
||||
}
|
||||
|
||||
$condition = ['`id` = ? AND (`term-date` > ? OR `archive`)', $contact['id'], DBA::NULL_DATETIME];
|
||||
$exists = DBA::exists('contact', $condition);
|
||||
|
||||
// We don't need to update, we never marked this contact for archival
|
||||
if (!$exists) {
|
||||
$condition = ['`id` = ? AND (`term-date` > ? OR `archive`)', $contact['id'], DBA::NULL_DATETIME];
|
||||
if (!DBA::exists('contact', $condition)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::info('Contact is marked as vital again', ['id' => $contact['id'], 'term-date' => $contact['term-date']]);
|
||||
|
||||
if (!isset($contact['url']) && !empty($contact['id'])) {
|
||||
$fields = ['id', 'url', 'batch'];
|
||||
if ((!isset($contact['url']) || !isset($contact['uri-id'])) && !empty($contact['id'])) {
|
||||
$fields = ['id', 'uri-id', 'url', 'batch', 'term-date'];
|
||||
$contact = DBA::selectFirst('contact', $fields, ['id' => $contact['id']]);
|
||||
if (!DBA::isResult($contact)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Logger::info('Contact is marked as vital again', ['id' => $contact['id'], 'term-date' => $contact['term-date'], 'url' => $contact['url']]);
|
||||
|
||||
// It's a miracle. Our dead contact has inexplicably come back to life.
|
||||
$fields = ['failed' => false, 'term-date' => DBA::NULL_DATETIME, 'archive' => false];
|
||||
self::update($fields, ['id' => $contact['id']]);
|
||||
self::update($fields, ['nurl' => Strings::normaliseLink($contact['url']), 'self' => false]);
|
||||
self::update($fields, ['uri-id' => $contact['uri-id'], 'self' => false]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1225,7 +1220,7 @@ class Contact
|
|||
if ($contact['uid'] && in_array($contact['rel'], [self::SHARING, self::FRIEND])) {
|
||||
$unfollow_link = 'contact/unfollow?url=' . urlencode($contact['url']) . '&auto=1';
|
||||
} elseif (!$contact['pending']) {
|
||||
$follow_link = 'contact/follow?url=' . urlencode($contact['url']) . '&auto=1';
|
||||
$follow_link = 'contact/follow?binurl=' . bin2hex($contact['url']) . '&auto=1';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1372,7 +1367,7 @@ class Contact
|
|||
$personal_contact = DBA::selectFirst('contact', $fields, ["`nurl` = ? AND `uid` != 0", Strings::normaliseLink($url)]);
|
||||
}
|
||||
|
||||
if (DBA::isResult($personal_contact)) {
|
||||
if (DBA::isResult($personal_contact) && !Probe::isProbable($personal_contact['network'])) {
|
||||
Logger::info('Take contact data from personal contact', ['url' => $url, 'update' => $update, 'contact' => $personal_contact]);
|
||||
$data = $personal_contact;
|
||||
$data['photo'] = $personal_contact['avatar'];
|
||||
|
@ -1590,11 +1585,15 @@ class Contact
|
|||
*/
|
||||
public static function getPostsFromId(int $cid, int $uid, bool $only_media = false, string $last_created = null): string
|
||||
{
|
||||
$contact = DBA::selectFirst('contact', ['contact-type', 'network'], ['id' => $cid]);
|
||||
$contact = DBA::selectFirst('contact', ['contact-type', 'network', 'name', 'nick'], ['id' => $cid]);
|
||||
if (!DBA::isResult($contact)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (Contact\User::isIsBlocked($cid, $uid)) {
|
||||
return DI::l10n()->t('%s has blocked you', $contact['name'] ?: $contact['nick']);
|
||||
}
|
||||
|
||||
if (empty($contact["network"]) || in_array($contact["network"], Protocol::FEDERATED)) {
|
||||
$condition = ["(`uid` = 0 OR (`uid` = ? AND NOT `global`))", $uid];
|
||||
} else {
|
||||
|
@ -1658,11 +1657,15 @@ class Contact
|
|||
*/
|
||||
public static function getThreadsFromId(int $cid, int $uid, int $update = 0, int $parent = 0, string $last_created = ''): string
|
||||
{
|
||||
$contact = DBA::selectFirst('contact', ['contact-type', 'network'], ['id' => $cid]);
|
||||
$contact = DBA::selectFirst('contact', ['contact-type', 'network', 'name', 'nick'], ['id' => $cid]);
|
||||
if (!DBA::isResult($contact)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (Contact\User::isIsBlocked($cid, $uid)) {
|
||||
return DI::l10n()->t('%s has blocked you', $contact['name'] ?: $contact['nick']);
|
||||
}
|
||||
|
||||
if (empty($contact["network"]) || in_array($contact["network"], Protocol::FEDERATED)) {
|
||||
$condition = ["(`uid` = 0 OR (`uid` = ? AND NOT `global`))", $uid];
|
||||
} else {
|
||||
|
@ -2298,7 +2301,7 @@ class Contact
|
|||
return;
|
||||
}
|
||||
|
||||
if (!Network::isValidHttpUrl($avatar)) {
|
||||
if (!empty($avatar) && !Network::isValidHttpUrl($avatar)) {
|
||||
Logger::warning('Invalid avatar', ['cid' => $cid, 'avatar' => $avatar]);
|
||||
$avatar = '';
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace Friendica\Model\Contact;
|
|||
|
||||
use Exception;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Core\Worker;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
|
@ -140,13 +140,21 @@ class User
|
|||
|
||||
$contact = Contact::getById($cdata['public']);
|
||||
if ($blocked) {
|
||||
Protocol::block($contact, $uid);
|
||||
Worker::add(Worker::PRIORITY_HIGH, 'Contact\Block', $cid, $uid);
|
||||
} else {
|
||||
Protocol::unblock($contact, $uid);
|
||||
Worker::add(Worker::PRIORITY_HIGH, 'Contact\Unblock', $cid, $uid);
|
||||
}
|
||||
|
||||
if ($cdata['user'] != 0) {
|
||||
DBA::update('contact', ['blocked' => $blocked], ['id' => $cdata['user'], 'pending' => false]);
|
||||
|
||||
if ($blocked) {
|
||||
$contact = Contact::getById($cdata['user']);
|
||||
if (!empty($contact)) {
|
||||
// Mastodon-expected behavior: relationship is severed on block
|
||||
Contact::terminateFriendship($contact);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DBA::update('user-contact', ['blocked' => $blocked], ['cid' => $cdata['public'], 'uid' => $uid], true);
|
||||
|
|
|
@ -30,6 +30,7 @@ class Conversation
|
|||
const PARCEL_ATOM03 = 15;
|
||||
const PARCEL_OPML = 16;
|
||||
const PARCEL_TWITTER = 67;
|
||||
const PARCEL_CONNECTOR = 68;
|
||||
const PARCEL_UNKNOWN = 255;
|
||||
|
||||
/**
|
||||
|
|
|
@ -753,6 +753,10 @@ class GServer
|
|||
$serverdata = self::detectNetworkViaContacts($url, $serverdata);
|
||||
}
|
||||
|
||||
if ($serverdata['platform'] == 'mastodon') {
|
||||
$serverdata = self::detectMastodonForks($serverdata);
|
||||
}
|
||||
|
||||
if (($serverdata['network'] == Protocol::PHANTOM) && in_array($serverdata['detection-method'], [self::DETECT_MANUAL, self::DETECT_BODY])) {
|
||||
self::setFailureByUrl($url);
|
||||
return false;
|
||||
|
@ -1792,6 +1796,23 @@ class GServer
|
|||
return $serverdata;
|
||||
}
|
||||
|
||||
private static function detectMastodonForks(array $serverdata): array
|
||||
{
|
||||
if (strpos($serverdata['version'], 'glitch') !== false) {
|
||||
$serverdata['platform'] = 'glitchsoc';
|
||||
}
|
||||
|
||||
if (strpos($serverdata['version'], 'chuckya') !== false) {
|
||||
$serverdata['platform'] = 'chuckya';
|
||||
}
|
||||
|
||||
if (strpos($serverdata['version'], 'sakura') !== false) {
|
||||
$serverdata['platform'] = 'sakura';
|
||||
}
|
||||
|
||||
return $serverdata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given server does have a '/poco' endpoint.
|
||||
* This is used for the 'PortableContact' functionality,
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
namespace Friendica\Model;
|
||||
|
||||
use Friendica\Contact\LocalRelationship\Entity\LocalRelationship;
|
||||
use Friendica\Content\ContactSelector;
|
||||
use Friendica\Content\Image;
|
||||
use Friendica\Content\Post\Collection\PostMedias;
|
||||
use Friendica\Content\Post\Entity\PostMedia;
|
||||
|
@ -3375,7 +3376,7 @@ class Item
|
|||
$item['body'] = preg_replace("#\s*\[attachment .*?].*?\[/attachment]\s*#ism", "\n", $item['body']);
|
||||
}
|
||||
|
||||
$fields = ['uri-id', 'uri', 'body', 'title', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink', 'network', 'has-media', 'quote-uri-id', 'post-type'];
|
||||
$fields = ['uri-id', 'uri', 'body', 'title', 'author-name', 'author-link', 'author-avatar', 'author-gsid', 'guid', 'created', 'plink', 'network', 'has-media', 'quote-uri-id', 'post-type'];
|
||||
|
||||
$shared_uri_id = 0;
|
||||
$shared_links = [];
|
||||
|
@ -3383,7 +3384,7 @@ class Item
|
|||
|
||||
$shared = DI::contentItem()->getSharedPost($item, $fields);
|
||||
if (!empty($shared['post'])) {
|
||||
$shared_item = $shared['post'];
|
||||
$shared_item = $shared['post'];
|
||||
$shared_item['body'] = Post\Media::removeFromEndOfBody($shared_item['body']);
|
||||
$shared_item['body'] = Post\Media::replaceImage($shared_item['body']);
|
||||
$quote_uri_id = $shared['post']['uri-id'];
|
||||
|
@ -3470,6 +3471,10 @@ class Item
|
|||
unset($hook_data);
|
||||
}
|
||||
|
||||
if (!empty($shared_item['uri-id'])) {
|
||||
$s = self::replacePlatformIcon($s, $shared_item, $uid);
|
||||
}
|
||||
|
||||
$hook_data = [
|
||||
'item' => $item,
|
||||
'html' => $s,
|
||||
|
@ -3533,6 +3538,39 @@ class Item
|
|||
return $hook_data['html'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the platform icon with the icon in the style selected by the user
|
||||
*
|
||||
* @param string $html
|
||||
* @param array $item
|
||||
* @param integer $uid
|
||||
* @return string
|
||||
*/
|
||||
private static function replacePlatformIcon(string $html, array $item, int $uid): string
|
||||
{
|
||||
$dom = new \DOMDocument();
|
||||
if (!@$dom->loadHTML($html)) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
$svg = ContactSelector::networkToSVG($item['network'], $item['author-gsid'], '', $uid);
|
||||
if (empty($svg)) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
$xpath = new \DOMXPath($dom);
|
||||
/** @var DOMElement $element */
|
||||
foreach ($xpath->query("//img[@class='network-svg']") as $element) {
|
||||
$src = $element->getAttributeNode('src')->nodeValue;
|
||||
if ($src == $svg) {
|
||||
continue;
|
||||
}
|
||||
$element_html = $element->ownerDocument->saveHTML($element);
|
||||
$html = str_replace($element_html, str_replace($src, $svg, $element_html), $html);
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify links to pictures to links for the "Fancybox" gallery
|
||||
*
|
||||
|
@ -3733,6 +3771,9 @@ class Item
|
|||
continue;
|
||||
}
|
||||
|
||||
if (empty($PostMedia->description) && DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'accessibility', 'hide_empty_descriptions')) {
|
||||
continue;
|
||||
}
|
||||
$images[] = $PostMedia->withUrl(new Uri($src_url))->withPreview(new Uri($preview_url), $preview_size);
|
||||
}
|
||||
}
|
||||
|
@ -4152,6 +4193,10 @@ class Item
|
|||
|
||||
try {
|
||||
$curlResult = DI::httpClient()->head($uri, [HttpClientOptions::ACCEPT_CONTENT => HttpClientAccept::JSON_AS, HttpClientOptions::REQUEST => HttpClientRequest::ACTIVITYPUB]);
|
||||
if (!HTTPSignature::isValidContentType($curlResult->getContentType(), $uri) && (current(explode(';', $curlResult->getContentType())) == 'application/json')) {
|
||||
// Issue 14126: Workaround for Mastodon servers that return "application/json" on a "head" request.
|
||||
$curlResult = HTTPSignature::fetchRaw($uri, $uid);
|
||||
}
|
||||
if (HTTPSignature::isValidContentType($curlResult->getContentType(), $uri)) {
|
||||
$fetched_uri = ActivityPub\Processor::fetchMissingActivity($uri, [], '', $completion, $uid);
|
||||
}
|
||||
|
|
|
@ -465,7 +465,7 @@ class Post
|
|||
AND ((NOT `contact-readonly` AND NOT `contact-pending` AND (`contact-rel` IN (?, ?)))
|
||||
OR `self` OR `contact-uid` = ?)
|
||||
AND NOT EXISTS(SELECT `uri-id` FROM `post-user` WHERE `uid` = ? AND `uri-id` = " . DBA::quoteIdentifier($view) . ".`uri-id` AND `hidden`)
|
||||
AND NOT EXISTS(SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `cid` IN (`author-id`, `owner-id`) AND (`blocked` OR `ignored`))
|
||||
AND NOT EXISTS(SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `cid` IN (`author-id`, `owner-id`) AND (`blocked` OR `ignored` OR `is-blocked`))
|
||||
AND NOT EXISTS(SELECT `gsid` FROM `user-gserver` WHERE `uid` = ? AND `gsid` IN (`author-gsid`, `owner-gsid`, `causer-gsid`) AND `ignored`)",
|
||||
0, Contact::SHARING, Contact::FRIEND, 0, $uid, $uid, $uid]);
|
||||
|
||||
|
|
|
@ -325,7 +325,7 @@ class Profile
|
|||
if ($visitor_is_following) {
|
||||
$unfollow_link = $visitor_base_path . '/contact/unfollow?url=' . urlencode($profile_url) . '&auto=1';
|
||||
} else {
|
||||
$follow_link = $visitor_base_path . '/contact/follow?url=' . urlencode($profile_url) . '&auto=1';
|
||||
$follow_link = $visitor_base_path . '/contact/follow?binurl=' . bin2hex($profile_url) . '&auto=1';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ class Index extends BaseAdmin
|
|||
'$function' => 'addons',
|
||||
'$addons' => $addons,
|
||||
'$pcount' => count($addons),
|
||||
'$noplugshint' => DI::l10n()->t('There are currently no addons available on your node. You can find the official addon repository at %1$s and might find other interesting addons in the open addon registry at %2$s', 'https://github.com/friendica/friendica-addons', 'http://addons.friendi.ca'),
|
||||
'$noplugshint' => DI::l10n()->t('There are currently no addons available on your node. You can find the official addon repository at %1$s.', 'https://git.friendi.ca/friendica/friendica-addons'),
|
||||
'$form_security_token' => self::getFormSecurityToken('admin_addons'),
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
namespace Friendica\Module\Admin;
|
||||
|
||||
use Friendica\App;
|
||||
use Friendica\Content\ContactSelector;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\Database\DBA;
|
||||
|
@ -36,6 +37,7 @@ class Federation extends BaseAdmin
|
|||
'foundkey' => ['name' => 'Foundkey', 'color' => '#609926'], // Some random color from the repository
|
||||
'funkwhale' => ['name' => 'Funkwhale', 'color' => '#4082B4'], // From the homepage
|
||||
'gancio' => ['name' => 'Gancio', 'color' => '#7253ed'], // Fontcolor from the page
|
||||
'glitchsoc' => ['name' => 'Mastodon Glitch Edition', 'color' => '#82dcb9'], // Color from their site
|
||||
'gnusocial' => ['name' => 'GNU Social/Statusnet', 'color' => '#a22430'], // dark red from the logo
|
||||
'gotosocial' => ['name' => 'GoToSocial', 'color' => '#df8958'], // Some color from their mascot
|
||||
'hometown' => ['name' => 'Hometown', 'color' => '#1f70c1'], // Color from the Patreon page
|
||||
|
@ -128,6 +130,10 @@ class Federation extends BaseAdmin
|
|||
$platform = 'nomad';
|
||||
} elseif(stristr($platform, 'pleroma')) {
|
||||
$platform = 'pleroma';
|
||||
} elseif(stristr($platform, 'glitchsoc')) {
|
||||
$platform = 'glitchsoc';
|
||||
} elseif(stristr($platform, 'iceshrimp.net')) {
|
||||
$platform = 'iceshrimp';
|
||||
} elseif(stristr($platform, 'statusnet')) {
|
||||
$platform = 'gnusocial';
|
||||
} elseif(stristr($platform, 'nextcloud')) {
|
||||
|
@ -176,6 +182,7 @@ class Federation extends BaseAdmin
|
|||
}
|
||||
|
||||
$gserver['platform'] = $systems[$platform]['name'];
|
||||
$gserver['svg'] = ContactSelector::networkToSVG($gserver['network'], null, $platform, DI::userSession()->getLocalUserId());
|
||||
$gserver['totallbl'] = DI::l10n()->tt('%2$s total system' , '%2$s total systems' , $gserver['total'], number_format($gserver['total']));
|
||||
$gserver['monthlbl'] = DI::l10n()->tt('%2$s active user last month' , '%2$s active users last month' , $gserver['month'] ?? 0, number_format($gserver['month'] ?? 0));
|
||||
$gserver['halfyearlbl'] = DI::l10n()->tt('%2$s active user last six months' , '%2$s active users last six months' , $gserver['halfyear'] ?? 0, number_format($gserver['halfyear'] ?? 0));
|
||||
|
|
|
@ -64,23 +64,19 @@ class Summary extends BaseAdmin
|
|||
|
||||
// Check if github.com/friendica/stable/VERSION is higher then
|
||||
// the local version of Friendica. Check is opt-in, source may be stable or develop branch
|
||||
if (DI::config()->get('system', 'check_new_version_url', 'none') != 'none') {
|
||||
$gitversion = DI::keyValue()->get('git_friendica_version') ?? '';
|
||||
|
||||
if (version_compare(App::VERSION, $gitversion) < 0) {
|
||||
$warningtext[] = DI::l10n()->t('There is a new version of Friendica available for download. Your current version is %1$s, upstream version is %2$s', App::VERSION, $gitversion);
|
||||
}
|
||||
if (Update::isAvailable()) {
|
||||
$warningtext[] = DI::l10n()->t('There is a new version of Friendica available for download. Your current version is %1$s, upstream version is %2$s', App::VERSION, Update::getAvailableVersion());
|
||||
}
|
||||
|
||||
if (DI::config()->get('system', 'dbupdate', DBStructure::UPDATE_NOT_CHECKED) == DBStructure::UPDATE_NOT_CHECKED) {
|
||||
if (DBStructure::getUpdateStatus() == DBStructure::UPDATE_NOT_CHECKED) {
|
||||
DBStructure::performUpdate();
|
||||
}
|
||||
|
||||
if (DI::config()->get('system', 'dbupdate') == DBStructure::UPDATE_FAILED) {
|
||||
if (DBStructure::getUpdateStatus() == DBStructure::UPDATE_FAILED) {
|
||||
$warningtext[] = DI::l10n()->t('The database update failed. Please run "php bin/console.php dbstructure update" from the command line and have a look at the errors that might appear.');
|
||||
}
|
||||
|
||||
if (DI::config()->get('system', 'update') == Update::FAILED) {
|
||||
if (Update::getStatus() == Update::FAILED) {
|
||||
$warningtext[] = DI::l10n()->t('The last update failed. Please run "php bin/console.php dbstructure update" from the command line and have a look at the errors that might appear. (Some of the errors are possibly inside the logfile.)');
|
||||
}
|
||||
|
||||
|
@ -160,9 +156,6 @@ class Summary extends BaseAdmin
|
|||
// We can do better, but this is a quick queue status
|
||||
$queues = ['label' => DI::l10n()->t('Message queues'), 'deferred' => $deferred, 'workerq' => $workerqueue];
|
||||
|
||||
$variables = DBA::toArray(DBA::p('SHOW variables LIKE "max_allowed_packet"'));
|
||||
$max_allowed_packet = $variables ? $variables[0]['Value'] : 0;
|
||||
|
||||
$server_settings = [
|
||||
'label' => DI::l10n()->t('Server Settings'),
|
||||
'php' => [
|
||||
|
@ -173,7 +166,7 @@ class Summary extends BaseAdmin
|
|||
'memory_limit' => ini_get('memory_limit')
|
||||
],
|
||||
'mysql' => [
|
||||
'max_allowed_packet' => $max_allowed_packet
|
||||
'max_allowed_packet' => DBA::getVariable('max_allowed_packet'),
|
||||
]
|
||||
];
|
||||
|
||||
|
|
|
@ -7,10 +7,8 @@
|
|||
|
||||
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;
|
||||
|
||||
/**
|
||||
|
@ -29,15 +27,6 @@ class Block extends BaseApi
|
|||
|
||||
Contact\User::setBlocked($this->parameters['id'], $uid, true);
|
||||
|
||||
$ucid = Contact::getUserContactId($this->parameters['id'], $uid);
|
||||
if ($ucid) {
|
||||
$contact = Contact::getById($ucid);
|
||||
if (!empty($contact)) {
|
||||
// Mastodon-expected behavior: relationship is severed on block
|
||||
Contact::terminateFriendship($contact);
|
||||
}
|
||||
}
|
||||
|
||||
$this->jsonExit(DI::mstdnRelationship()->createFromContactId($this->parameters['id'], $uid)->toArray());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,28 +59,34 @@ class Apps extends BaseApi
|
|||
$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity($this->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' => $request['client_name'], 'redirect_uri' => $request['redirect_uris']];
|
||||
$fields = ['name' => $request['client_name'], 'redirect_uri' => $request['redirect_uris']];
|
||||
|
||||
if (!empty($request['scopes'])) {
|
||||
$fields['scopes'] = $request['scopes'];
|
||||
}
|
||||
|
||||
$fields['read'] = (stripos($request['scopes'], self::SCOPE_READ) !== false);
|
||||
$fields['write'] = (stripos($request['scopes'], self::SCOPE_WRITE) !== false);
|
||||
$fields['follow'] = (stripos($request['scopes'], self::SCOPE_FOLLOW) !== false);
|
||||
$fields['push'] = (stripos($request['scopes'], self::SCOPE_PUSH) !== false);
|
||||
|
||||
if (!empty($request['website'])) {
|
||||
$fields['website'] = $request['website'];
|
||||
}
|
||||
|
||||
$application = DBA::selectFirst('application', ['id'], $fields);
|
||||
if (!empty($application['id'])) {
|
||||
$this->logger->debug('Found existing application', ['request' => $request, 'id' => $application['id']]);
|
||||
$this->jsonExit(DI::mstdnApplication()->createFromApplicationId($application['id'])->toArray());
|
||||
}
|
||||
|
||||
$fields['read'] = (stripos($request['scopes'], self::SCOPE_READ) !== false);
|
||||
$fields['write'] = (stripos($request['scopes'], self::SCOPE_WRITE) !== false);
|
||||
$fields['follow'] = (stripos($request['scopes'], self::SCOPE_FOLLOW) !== false);
|
||||
$fields['push'] = (stripos($request['scopes'], self::SCOPE_PUSH) !== false);
|
||||
$fields['client_id'] = bin2hex(random_bytes(32));
|
||||
$fields['client_secret'] = bin2hex(random_bytes(32));
|
||||
|
||||
if (!DBA::insert('application', $fields)) {
|
||||
$this->logAndJsonError(500, $this->errorFactory->InternalError());
|
||||
}
|
||||
|
||||
$this->logger->debug('Create new application', ['request' => $request, 'id' => DBA::lastInsertId()]);
|
||||
$this->jsonExit(DI::mstdnApplication()->createFromApplicationId(DBA::lastInsertId())->toArray());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,38 +42,37 @@ class Media extends BaseApi
|
|||
|
||||
$type = Post\Media::getType($request['file']['type']);
|
||||
|
||||
if (in_array($type, [Post\Media::IMAGE, Post\Media::UNKNOWN])) {
|
||||
if (in_array($type, [Post\Media::IMAGE, Post\Media::UNKNOWN, Post\Media::APPLICATION])) {
|
||||
$media = Photo::upload($uid, $request['file'], '', null, null, '', '', $request['description']);
|
||||
if (empty($media)) {
|
||||
$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity());
|
||||
if (!empty($media)) {
|
||||
Logger::info('Uploaded photo', ['media' => $media]);
|
||||
$this->jsonExit(DI::mstdnAttachment()->createFromPhoto($media['id']));
|
||||
} elseif ($type == Post\Media::IMAGE) {
|
||||
$this->jsonExit(DI::mstdnAttachment()->createFromPhoto($media['id']));
|
||||
}
|
||||
|
||||
Logger::info('Uploaded photo', ['media' => $media]);
|
||||
|
||||
$this->jsonExit(DI::mstdnAttachment()->createFromPhoto($media['id']));
|
||||
} else {
|
||||
$tempFileName = $request['file']['tmp_name'];
|
||||
$fileName = basename($request['file']['name']);
|
||||
$fileSize = intval($request['file']['size']);
|
||||
$maxFileSize = Strings::getBytesFromShorthand(DI::config()->get('system', 'maxfilesize'));
|
||||
|
||||
if ($fileSize <= 0) {
|
||||
Logger::notice('Filesize is invalid', ['size' => $fileSize, 'request' => $request]);
|
||||
@unlink($tempFileName);
|
||||
$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity());
|
||||
}
|
||||
|
||||
if ($maxFileSize && $fileSize > $maxFileSize) {
|
||||
Logger::notice('Filesize is too large', ['size' => $fileSize, 'max' => $maxFileSize, 'request' => $request]);
|
||||
@unlink($tempFileName);
|
||||
$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity());
|
||||
}
|
||||
|
||||
$id = Attach::storeFile($tempFileName, self::getCurrentUserID(), $fileName, $request['file']['type'], '<' . Contact::getPublicIdByUserId(self::getCurrentUserID()) . '>');
|
||||
@unlink($tempFileName);
|
||||
Logger::info('Uploaded media', ['id' => $id]);
|
||||
$this->jsonExit(DI::mstdnAttachment()->createFromAttach($id));
|
||||
}
|
||||
|
||||
$tempFileName = $request['file']['tmp_name'];
|
||||
$fileName = basename($request['file']['name']);
|
||||
$fileSize = intval($request['file']['size']);
|
||||
$maxFileSize = Strings::getBytesFromShorthand(DI::config()->get('system', 'maxfilesize'));
|
||||
|
||||
if ($fileSize <= 0) {
|
||||
Logger::notice('Filesize is invalid', ['size' => $fileSize, 'request' => $request]);
|
||||
@unlink($tempFileName);
|
||||
$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity());
|
||||
}
|
||||
|
||||
if ($maxFileSize && $fileSize > $maxFileSize) {
|
||||
Logger::notice('Filesize is too large', ['size' => $fileSize, 'max' => $maxFileSize, 'request' => $request]);
|
||||
@unlink($tempFileName);
|
||||
$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity());
|
||||
}
|
||||
|
||||
$id = Attach::storeFile($tempFileName, self::getCurrentUserID(), $fileName, $request['file']['type'], '<' . Contact::getPublicIdByUserId(self::getCurrentUserID()) . '>');
|
||||
@unlink($tempFileName);
|
||||
Logger::info('Uploaded media', ['id' => $id]);
|
||||
$this->jsonExit(DI::mstdnAttachment()->createFromAttach($id));
|
||||
}
|
||||
|
||||
public function put(array $request = [])
|
||||
|
|
|
@ -66,7 +66,7 @@ class Context extends BaseApi
|
|||
if (!empty($uid) && !$request['show_all']) {
|
||||
$condition = DBA::mergeConditions(
|
||||
$condition,
|
||||
["NOT `author-id` IN (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND (`blocked` OR `ignored`))", $uid]
|
||||
["NOT `author-id` IN (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND (`blocked` OR `ignored` OR `is-blocked`))", $uid]
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ class Direct extends BaseApi
|
|||
if (!empty($uid)) {
|
||||
$condition = DBA::mergeConditions(
|
||||
$condition,
|
||||
["NOT `parent-author-id` IN (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND (`blocked` OR `ignored`) AND `cid` = `parent-author-id`)", $uid]
|
||||
["NOT `parent-author-id` IN (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND (`blocked` OR `ignored` OR `is-blocked`) AND `cid` = `parent-author-id`)", $uid]
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -66,6 +66,8 @@ class Home extends BaseApi
|
|||
$condition = DBA::mergeConditions($condition, ['gravity' => Item::GRAVITY_PARENT]);
|
||||
}
|
||||
|
||||
$condition = DBA::mergeConditions($condition, ["NOT EXISTS(SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `cid` IN (`parent-owner-id`, `parent-author-id`) AND (`blocked` OR `ignored` OR `is-blocked` OR `channel-only`))", $uid]);
|
||||
|
||||
$items = Post::selectTimelineForUser($uid, ['uri-id'], $condition, $params);
|
||||
|
||||
$display_quotes = self::appSupportsQuotes();
|
||||
|
|
|
@ -93,7 +93,7 @@ class Tag extends BaseApi
|
|||
if (!empty($uid)) {
|
||||
$condition = DBA::mergeConditions(
|
||||
$condition,
|
||||
["NOT `author-id` IN (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND (`blocked` OR `ignored`) AND `cid` = `author-id`)", $uid]
|
||||
["NOT `author-id` IN (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND (`blocked` OR `ignored` OR `is-blocked`) AND `cid` = `author-id`)", $uid]
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -617,7 +617,7 @@ class Contact extends BaseModule
|
|||
'account_type' => Model\Contact::getAccountType($contact['contact-type']),
|
||||
'sparkle' => $sparkle,
|
||||
'itemurl' => ($contact['addr'] ?? '') ?: $contact['url'],
|
||||
'network' => ContactSelector::networkToName($contact['network'], $contact['url'], $contact['protocol'], $contact['gsid']),
|
||||
'network' => ContactSelector::networkToName($contact['network'], $contact['protocol'], $contact['gsid']),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,11 +56,15 @@ class Follow extends BaseModule
|
|||
throw new ForbiddenException($this->t('Access denied.'));
|
||||
}
|
||||
|
||||
if (isset($request['cancel']) || empty($request['url'])) {
|
||||
$this->baseUrl->redirect('contact');
|
||||
if (!empty($request['follow-url'])) {
|
||||
$this->baseUrl->redirect('contact/follow?binurl=' . bin2hex($request['follow-url']));
|
||||
}
|
||||
|
||||
$url = Probe::cleanURI($request['url']);
|
||||
$url = $this->getUrl($request);
|
||||
|
||||
if (isset($request['cancel']) || empty($url)) {
|
||||
$this->baseUrl->redirect('contact');
|
||||
}
|
||||
|
||||
$this->process($url);
|
||||
}
|
||||
|
@ -77,7 +81,7 @@ class Follow extends BaseModule
|
|||
$uid = $this->session->getLocalUserId();
|
||||
|
||||
// uri is used by the /authorize_interaction Mastodon route
|
||||
$url = Probe::cleanURI(trim($request['uri'] ?? $request['url'] ?? ''));
|
||||
$url = $this->getUrl($request);
|
||||
|
||||
// Issue 6874: Allow remote following from Peertube
|
||||
if (strpos($url, 'acct:') === 0) {
|
||||
|
@ -182,7 +186,7 @@ class Follow extends BaseModule
|
|||
|
||||
protected function process(string $url)
|
||||
{
|
||||
$returnPath = 'contact/follow?url=' . urlencode($url);
|
||||
$returnPath = 'contact/follow?binurl=' . bin2hex($url);
|
||||
|
||||
$result = Contact::createFromProbeForUser($this->session->getLocalUserId(), $url);
|
||||
|
||||
|
@ -227,4 +231,14 @@ class Follow extends BaseModule
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private function getUrl(array $request): string
|
||||
{
|
||||
if (!empty($request['binurl']) && Strings::isHex($request['binurl'])) {
|
||||
$url = hex2bin($request['binurl']);
|
||||
} else {
|
||||
$url = $request['url'] ?? '';
|
||||
}
|
||||
return Probe::cleanURI($url);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -297,7 +297,7 @@ class Profile extends BaseModule
|
|||
|
||||
$poll_enabled = in_array($contact['network'], [Protocol::DFRN, Protocol::FEED, Protocol::MAIL]);
|
||||
|
||||
$nettype = $this->t('Network type: %s', ContactSelector::networkToName($contact['network'], $contact['url'], $contact['protocol'], $contact['gsid']));
|
||||
$nettype = $this->t('Network type: %s', ContactSelector::networkToName($contact['network'], $contact['protocol'], $contact['gsid']));
|
||||
|
||||
// tabs
|
||||
$tab_str = Module\Contact::getTabsHTML($contact, Module\Contact::TAB_PROFILE);
|
||||
|
@ -356,6 +356,11 @@ class Profile extends BaseModule
|
|||
|
||||
$contact_actions = $this->getContactActions($contact, $localRelationship);
|
||||
|
||||
if (Contact\User::isIsBlocked($contact['id'], $this->session->getLocalUserId())) {
|
||||
$relation_text = $this->t('%s has blocked you', $contact['name'] ?: $contact['nick']);
|
||||
unset($contact_actions['follow']);
|
||||
}
|
||||
|
||||
if ($localRelationship->rel !== Contact::NOTHING) {
|
||||
$lbl_info1 = $this->t('Contact Information / Notes');
|
||||
$contact_settings_label = $this->t('Contact Settings');
|
||||
|
@ -477,7 +482,7 @@ class Profile extends BaseModule
|
|||
} else {
|
||||
$contact_actions['follow'] = [
|
||||
'label' => $this->t('Follow'),
|
||||
'url' => 'contact/follow?url=' . urlencode($contact['url']) . '&auto=1',
|
||||
'url' => 'contact/follow?binurl=' . bin2hex($contact['url']) . '&auto=1',
|
||||
'title' => '',
|
||||
'sel' => '',
|
||||
'id' => 'follow',
|
||||
|
|
|
@ -87,7 +87,7 @@ class Install extends BaseModule
|
|||
// so we may not have a css at all. Here we set a static css file for the install procedure pages
|
||||
Renderer::$theme['stylesheet'] = $this->baseUrl . '/view/install/style.css';
|
||||
|
||||
$this->currentWizardStep = ($_POST['pass'] ?? '') ?: self::SYSTEM_CHECK;
|
||||
$this->currentWizardStep = ($_REQUEST['pass'] ?? '') ?: self::SYSTEM_CHECK;
|
||||
}
|
||||
|
||||
protected function post(array $request = [])
|
||||
|
@ -164,6 +164,7 @@ class Install extends BaseModule
|
|||
|
||||
break;
|
||||
}
|
||||
DI::baseUrl()->redirect('install?pass=' . $this->currentWizardStep);
|
||||
}
|
||||
|
||||
protected function content(array $request = []): string
|
||||
|
|
|
@ -96,7 +96,7 @@ class Add extends BaseModeration
|
|||
|
||||
array_walk($gservers, function (array &$gserver) {
|
||||
$gserver['domain'] = (new Uri($gserver['url']))->getHost();
|
||||
$gserver['network_icon'] = ContactSelector::networkToIcon($gserver['network']);
|
||||
$gserver['network_svg'] = ContactSelector::networkToSVG($gserver['network']);
|
||||
$gserver['network_name'] = ContactSelector::networkToName($gserver['network']);
|
||||
});
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ class Source extends BaseModeration
|
|||
'urllbl' => $this->t('URL'),
|
||||
'mentionlbl' => $this->t('Mention'),
|
||||
'implicitlbl' => $this->t('Implicit Mention'),
|
||||
'error' => $this->t('Error'),
|
||||
'error' => $this->tt('Error','Errors', 1),
|
||||
'notfound' => $this->t('Item not found'),
|
||||
'nosource' => $this->t('No source recorded'),
|
||||
'noconfig' => !$this->config->get('debug', 'store_source') ? $this->t('Please make sure the <code>debug.store_source</code> config key is set in <code>config/local.config.php</code> for future items to have sources.') : '',
|
||||
|
|
|
@ -156,7 +156,8 @@ class Introductions extends BaseNotifications
|
|||
$header .= ' <' . $Introduction->getAddr() . '>';
|
||||
}
|
||||
|
||||
$header .= ' (' . ContactSelector::networkToName($Introduction->getNetwork(), $Introduction->getUrl()) . ')';
|
||||
$gsid = ContactSelector::getServerIdForProfile($Introduction->getUrl());
|
||||
$header .= ' (' . ContactSelector::networkToName($Introduction->getNetwork(), '', $gsid) . ')';
|
||||
|
||||
if ($Introduction->getNetwork() != Protocol::DIASPORA) {
|
||||
$discard = $this->t('Discard');
|
||||
|
@ -191,7 +192,7 @@ class Introductions extends BaseNotifications
|
|||
'$addr' => $Introduction->getAddr(),
|
||||
'$lbl_knowyou' => $lbl_knowyou,
|
||||
'$lbl_network' => $this->t('Network:'),
|
||||
'$network' => ContactSelector::networkToName($Introduction->getNetwork(), $Introduction->getUrl()),
|
||||
'$network' => ContactSelector::networkToName($Introduction->getNetwork(), '', $gsid),
|
||||
'$knowyou' => $knowyou,
|
||||
'$approve' => $this->t('Approve'),
|
||||
'$note' => $Introduction->getNote(),
|
||||
|
|
|
@ -36,17 +36,18 @@ class Authorize extends BaseApi
|
|||
], $request);
|
||||
|
||||
if ($request['response_type'] != 'code') {
|
||||
Logger::warning('Unsupported or missing response type', ['request' => $_REQUEST]);
|
||||
Logger::warning('Unsupported or missing response type', ['request' => $request]);
|
||||
$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity($this->t('Unsupported or missing response type')));
|
||||
}
|
||||
|
||||
if (empty($request['client_id']) || empty($request['redirect_uri'])) {
|
||||
Logger::warning('Incomplete request data', ['request' => $_REQUEST]);
|
||||
Logger::warning('Incomplete request data', ['request' => $request]);
|
||||
$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity($this->t('Incomplete request data')));
|
||||
}
|
||||
|
||||
$application = OAuth::getApplication($request['client_id'], $request['client_secret'], $request['redirect_uri']);
|
||||
if (empty($application)) {
|
||||
Logger::warning('An application could not be fetched.', ['request' => $request]);
|
||||
$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity());
|
||||
}
|
||||
|
||||
|
|
|
@ -9,10 +9,14 @@ namespace Friendica\Module;
|
|||
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
use Friendica\App;
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\DI;
|
||||
use Friendica\Core\Config\Capability\IManageConfigValues;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Util\BasePath;
|
||||
use Friendica\Util\Profiler;
|
||||
use Friendica\Util\XML;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Prints the opensearch description document
|
||||
|
@ -20,22 +24,37 @@ use Friendica\Util\XML;
|
|||
*/
|
||||
class OpenSearch extends BaseModule
|
||||
{
|
||||
/** @var IManageConfigValues */
|
||||
private $config;
|
||||
|
||||
/** @var App\baseUrl */
|
||||
protected $baseUrl;
|
||||
|
||||
/** @var string */
|
||||
private $basePath;
|
||||
|
||||
public function __construct(BasePath $basePath, IManageConfigValues $config, L10n $l10n, App\baseUrl $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
|
||||
{
|
||||
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
|
||||
|
||||
$this->config = $config;
|
||||
$this->basePath = $basePath->getPath();
|
||||
$this->baseUrl = $baseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function rawContent(array $request = [])
|
||||
{
|
||||
$hostname = DI::baseUrl()->getHost();
|
||||
$baseUrl = (string)DI::baseUrl();
|
||||
|
||||
/** @var DOMDocument $xml */
|
||||
XML::fromArray([
|
||||
'OpenSearchDescription' => [
|
||||
'@attributes' => [
|
||||
'xmlns' => 'http://a9.com/-/spec/opensearch/1.1',
|
||||
'xmlns' => 'http://a9.com/-/spec/opensearch/1.1/',
|
||||
],
|
||||
'ShortName' => "Friendica $hostname",
|
||||
'Description' => "Search in Friendica $hostname",
|
||||
'ShortName' => $this->baseUrl->getHost(),
|
||||
'Description' => $this->l10n->t('Search in Friendica %s', $this->baseUrl->getHost()),
|
||||
'Contact' => 'https://github.com/friendica/friendica/issues',
|
||||
'InputEncoding' => 'UTF-8',
|
||||
'OutputEncoding' => 'UTF-8',
|
||||
|
@ -46,29 +65,41 @@ class OpenSearch extends BaseModule
|
|||
/** @var DOMElement $parent */
|
||||
$parent = $xml->getElementsByTagName('OpenSearchDescription')[0];
|
||||
|
||||
XML::addElement($xml, $parent, 'Image',
|
||||
"$baseUrl/images/friendica-16.png", [
|
||||
if (file_exists($this->basePath . '/favicon.ico')) {
|
||||
$shortcut_icon = '/favicon.ico';
|
||||
} else {
|
||||
$shortcut_icon = $this->config->get('system', 'shortcut_icon');
|
||||
}
|
||||
|
||||
if (!empty($shortcut_icon)) {
|
||||
$imagedata = getimagesize($this->baseUrl . $shortcut_icon);
|
||||
}
|
||||
|
||||
if (!empty($imagedata)) {
|
||||
XML::addElement($xml, $parent, 'Image', $this->baseUrl . $shortcut_icon, [
|
||||
'width' => $imagedata[0],
|
||||
'height' => $imagedata[1],
|
||||
'type' => $imagedata['mime'],
|
||||
]);
|
||||
} else {
|
||||
XML::addElement($xml, $parent, 'Image',
|
||||
$this->baseUrl . '/images/friendica-16.png', [
|
||||
'height' => 16,
|
||||
'width' => 16,
|
||||
'type' => 'image/png',
|
||||
]);
|
||||
|
||||
XML::addElement($xml, $parent, 'Image',
|
||||
"$baseUrl/images/friendica-64.png", [
|
||||
'height' => 64,
|
||||
'width' => 64,
|
||||
'type' => 'image/png',
|
||||
]);
|
||||
}
|
||||
|
||||
XML::addElement($xml, $parent, 'Url', '', [
|
||||
'type' => 'text/html',
|
||||
'template' => "$baseUrl/search?search={searchTerms}",
|
||||
'method' => 'get',
|
||||
'template' => $this->baseUrl . '/search?q={searchTerms}',
|
||||
]);
|
||||
|
||||
XML::addElement($xml, $parent, 'Url', '', [
|
||||
'type' => 'application/opensearchdescription+xml',
|
||||
'rel' => 'self',
|
||||
'template' => "$baseUrl/opensearch",
|
||||
'template' => $this->baseUrl . '/opensearch',
|
||||
]);
|
||||
|
||||
$this->httpExit($xml->saveXML(), Response::TYPE_XML, 'application/opensearchdescription+xml');
|
||||
|
|
|
@ -225,7 +225,7 @@ class Conversations extends BaseProfile
|
|||
$items = array_merge($items, $pinned);
|
||||
}
|
||||
|
||||
$o .= $this->conversation->render($items, Conversation::MODE_PROFILE, false, false, 'pinned_received', $profile['uid']);
|
||||
$o .= $this->conversation->render($items, Conversation::MODE_PROFILE, false, false, 'pinned_received', $this->session->getLocalUserId());
|
||||
|
||||
$o .= $pager->renderMinimal(count($items));
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
namespace Friendica\Module\Settings;
|
||||
|
||||
use Friendica\App;
|
||||
use Friendica\Content\ContactSelector;
|
||||
use Friendica\Content\Conversation\Collection\Timelines;
|
||||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Content\Conversation\Factory\Channel as ChannelFactory;
|
||||
|
@ -80,23 +81,26 @@ class Display extends BaseSettings
|
|||
|
||||
$user = User::getById($uid);
|
||||
|
||||
$theme = trim($request['theme']);
|
||||
$mobile_theme = trim($request['mobile_theme'] ?? '');
|
||||
$enable_smile = (bool)$request['enable_smile'];
|
||||
$enable = (array)$request['enable'];
|
||||
$bookmark = (array)$request['bookmark'];
|
||||
$channel_languages = (array)$request['channel_languages'];
|
||||
$first_day_of_week = (bool)$request['first_day_of_week'];
|
||||
$calendar_default_view = trim($request['calendar_default_view']);
|
||||
$infinite_scroll = (bool)$request['infinite_scroll'];
|
||||
$enable_smart_threading = (bool)$request['enable_smart_threading'];
|
||||
$enable_dislike = (bool)$request['enable_dislike'];
|
||||
$display_resharer = (bool)$request['display_resharer'];
|
||||
$stay_local = (bool)$request['stay_local'];
|
||||
$show_page_drop = (bool)$request['show_page_drop'];
|
||||
$display_eventlist = (bool)$request['display_eventlist'];
|
||||
$preview_mode = (int)$request['preview_mode'];
|
||||
$browser_update = (int)$request['browser_update'];
|
||||
$theme = trim($request['theme']);
|
||||
$mobile_theme = trim($request['mobile_theme'] ?? '');
|
||||
$enable_smile = (bool)$request['enable_smile'];
|
||||
$enable = (array)$request['enable'];
|
||||
$bookmark = (array)$request['bookmark'];
|
||||
$channel_languages = (array)$request['channel_languages'];
|
||||
$first_day_of_week = (int)$request['first_day_of_week'];
|
||||
$calendar_default_view = trim($request['calendar_default_view']);
|
||||
$infinite_scroll = (bool)$request['infinite_scroll'];
|
||||
$enable_smart_threading = (bool)$request['enable_smart_threading'];
|
||||
$enable_dislike = (bool)$request['enable_dislike'];
|
||||
$display_resharer = (bool)$request['display_resharer'];
|
||||
$stay_local = (bool)$request['stay_local'];
|
||||
$hide_empty_descriptions = (bool)$request['hide_empty_descriptions'];
|
||||
$hide_custom_emojis = (bool)$request['hide_custom_emojis'];
|
||||
$platform_icon_style = (int)$request['platform_icon_style'];
|
||||
$show_page_drop = (bool)$request['show_page_drop'];
|
||||
$display_eventlist = (bool)$request['display_eventlist'];
|
||||
$preview_mode = (int)$request['preview_mode'];
|
||||
$browser_update = (int)$request['browser_update'];
|
||||
if ($browser_update != -1) {
|
||||
$browser_update = $browser_update * 1000;
|
||||
if ($browser_update < 10000) {
|
||||
|
@ -135,25 +139,29 @@ class Display extends BaseSettings
|
|||
$this->pConfig->set($uid, 'system', 'mobile_theme', $mobile_theme);
|
||||
}
|
||||
|
||||
$this->pConfig->set($uid, 'system', 'itemspage_network' , $itemspage_network);
|
||||
$this->pConfig->set($uid, 'system', 'itemspage_network', $itemspage_network);
|
||||
$this->pConfig->set($uid, 'system', 'itemspage_mobile_network', $itemspage_mobile_network);
|
||||
$this->pConfig->set($uid, 'system', 'update_interval' , $browser_update);
|
||||
$this->pConfig->set($uid, 'system', 'no_smilies' , !$enable_smile);
|
||||
$this->pConfig->set($uid, 'system', 'infinite_scroll' , $infinite_scroll);
|
||||
$this->pConfig->set($uid, 'system', 'no_smart_threading' , !$enable_smart_threading);
|
||||
$this->pConfig->set($uid, 'system', 'hide_dislike' , !$enable_dislike);
|
||||
$this->pConfig->set($uid, 'system', 'display_resharer' , $display_resharer);
|
||||
$this->pConfig->set($uid, 'system', 'stay_local' , $stay_local);
|
||||
$this->pConfig->set($uid, 'system', 'show_page_drop' , $show_page_drop);
|
||||
$this->pConfig->set($uid, 'system', 'display_eventlist' , $display_eventlist);
|
||||
$this->pConfig->set($uid, 'system', 'preview_mode' , $preview_mode);
|
||||
$this->pConfig->set($uid, 'system', 'update_interval', $browser_update);
|
||||
$this->pConfig->set($uid, 'system', 'no_smilies', !$enable_smile);
|
||||
$this->pConfig->set($uid, 'system', 'infinite_scroll', $infinite_scroll);
|
||||
$this->pConfig->set($uid, 'system', 'no_smart_threading', !$enable_smart_threading);
|
||||
$this->pConfig->set($uid, 'system', 'hide_dislike', !$enable_dislike);
|
||||
$this->pConfig->set($uid, 'system', 'display_resharer', $display_resharer);
|
||||
$this->pConfig->set($uid, 'system', 'stay_local', $stay_local);
|
||||
$this->pConfig->set($uid, 'system', 'show_page_drop', $show_page_drop);
|
||||
$this->pConfig->set($uid, 'system', 'display_eventlist', $display_eventlist);
|
||||
$this->pConfig->set($uid, 'system', 'preview_mode', $preview_mode);
|
||||
|
||||
$this->pConfig->set($uid, 'system', 'network_timelines' , $network_timelines);
|
||||
$this->pConfig->set($uid, 'system', 'enabled_timelines' , $enabled_timelines);
|
||||
$this->pConfig->set($uid, 'channel', 'languages' , $channel_languages);
|
||||
$this->pConfig->set($uid, 'system', 'network_timelines', $network_timelines);
|
||||
$this->pConfig->set($uid, 'system', 'enabled_timelines', $enabled_timelines);
|
||||
$this->pConfig->set($uid, 'channel', 'languages', $channel_languages);
|
||||
|
||||
$this->pConfig->set($uid, 'calendar', 'first_day_of_week' , $first_day_of_week);
|
||||
$this->pConfig->set($uid, 'calendar', 'default_view' , $calendar_default_view);
|
||||
$this->pConfig->set($uid, 'accessibility', 'hide_empty_descriptions', $hide_empty_descriptions);
|
||||
$this->pConfig->set($uid, 'accessibility', 'hide_custom_emojis', $hide_custom_emojis);
|
||||
$this->pConfig->set($uid, 'accessibility', 'platform_icon_style', $platform_icon_style);
|
||||
|
||||
$this->pConfig->set($uid, 'calendar', 'first_day_of_week', $first_day_of_week);
|
||||
$this->pConfig->set($uid, 'calendar', 'default_view', $calendar_default_view);
|
||||
|
||||
if (in_array($theme, Theme::getAllowedList())) {
|
||||
if ($theme == $user['theme']) {
|
||||
|
@ -241,6 +249,17 @@ class Display extends BaseSettings
|
|||
$show_page_drop = $this->pConfig->get($uid, 'system', 'show_page_drop', true);
|
||||
$display_eventlist = $this->pConfig->get($uid, 'system', 'display_eventlist', true);
|
||||
|
||||
$hide_empty_descriptions = $this->pConfig->get($uid, 'accessibility', 'hide_empty_descriptions', false);
|
||||
$hide_custom_emojis = $this->pConfig->get($uid, 'accessibility', 'hide_custom_emojis', false);
|
||||
$platform_icon_style = $this->pConfig->get($uid, 'accessibility', 'platform_icon_style', ContactSelector::SVG_COLOR_BLACK);
|
||||
$platform_icon_styles = [
|
||||
ContactSelector::SVG_DISABLED => $this->t('Disabled'),
|
||||
ContactSelector::SVG_COLOR_BLACK => $this->t('Color/Black'),
|
||||
ContactSelector::SVG_BLACK => $this->t('Black'),
|
||||
ContactSelector::SVG_COLOR_WHITE => $this->t('Color/White'),
|
||||
ContactSelector::SVG_WHITE => $this->t('White'),
|
||||
];
|
||||
|
||||
$preview_mode = $this->pConfig->get($uid, 'system', 'preview_mode', BBCode::PREVIEW_LARGE);
|
||||
$preview_modes = [
|
||||
BBCode::PREVIEW_NONE => $this->t('No preview'),
|
||||
|
@ -308,18 +327,21 @@ class Display extends BaseSettings
|
|||
'$mobile_theme' => ['mobile_theme', $this->t('Mobile Theme:'), $mobile_theme_selected, '', $mobile_themes, false],
|
||||
'$theme_config' => $theme_config,
|
||||
|
||||
'$itemspage_network' => ['itemspage_network' , $this->t('Number of items to display per page:'), $itemspage_network, $this->t('Maximum of 100 items')],
|
||||
'$itemspage_network' => ['itemspage_network', $this->t('Number of items to display per page:'), $itemspage_network, $this->t('Maximum of 100 items')],
|
||||
'$itemspage_mobile_network' => ['itemspage_mobile_network', $this->t('Number of items to display per page when viewed from mobile device:'), $itemspage_mobile_network, $this->t('Maximum of 100 items')],
|
||||
'$ajaxint' => ['browser_update' , $this->t('Update browser every xx seconds'), $browser_update, $this->t('Minimum of 10 seconds. Enter -1 to disable it.')],
|
||||
'$enable_smile' => ['enable_smile' , $this->t('Display emoticons'), $enable_smile, $this->t('When enabled, emoticons are replaced with matching symbols.')],
|
||||
'$infinite_scroll' => ['infinite_scroll' , $this->t('Infinite scroll'), $infinite_scroll, $this->t('Automatic fetch new items when reaching the page end.')],
|
||||
'$enable_smart_threading' => ['enable_smart_threading' , $this->t('Enable Smart Threading'), $enable_smart_threading, $this->t('Enable the automatic suppression of extraneous thread indentation.')],
|
||||
'$enable_dislike' => ['enable_dislike' , $this->t('Display the Dislike feature'), $enable_dislike, $this->t('Display the Dislike button and dislike reactions on posts and comments.')],
|
||||
'$display_resharer' => ['display_resharer' , $this->t('Display the resharer'), $display_resharer, $this->t('Display the first resharer as icon and text on a reshared item.')],
|
||||
'$stay_local' => ['stay_local' , $this->t('Stay local'), $stay_local, $this->t("Don't go to a remote system when following a contact link.")],
|
||||
'$show_page_drop' => ['show_page_drop' , $this->t('Show the post deletion checkbox'), $show_page_drop, $this->t("Display the checkbox for the post deletion on the network page.")],
|
||||
'$display_eventlist' => ['display_eventlist' , $this->t('DIsplay the event list'), $display_eventlist, $this->t("Display the birthday reminder and event list on the network page.")],
|
||||
'$preview_mode' => ['preview_mode' , $this->t('Link preview mode'), $preview_mode, $this->t('Appearance of the link preview that is added to each post with a link.'), $preview_modes, false],
|
||||
'$ajaxint' => ['browser_update', $this->t('Update browser every xx seconds'), $browser_update, $this->t('Minimum of 10 seconds. Enter -1 to disable it.')],
|
||||
'$enable_smile' => ['enable_smile', $this->t('Display emoticons'), $enable_smile, $this->t('When enabled, emoticons are replaced with matching symbols.')],
|
||||
'$infinite_scroll' => ['infinite_scroll', $this->t('Infinite scroll'), $infinite_scroll, $this->t('Automatic fetch new items when reaching the page end.')],
|
||||
'$enable_smart_threading' => ['enable_smart_threading', $this->t('Enable Smart Threading'), $enable_smart_threading, $this->t('Enable the automatic suppression of extraneous thread indentation.')],
|
||||
'$enable_dislike' => ['enable_dislike', $this->t('Display the Dislike feature'), $enable_dislike, $this->t('Display the Dislike button and dislike reactions on posts and comments.')],
|
||||
'$display_resharer' => ['display_resharer', $this->t('Display the resharer'), $display_resharer, $this->t('Display the first resharer as icon and text on a reshared item.')],
|
||||
'$stay_local' => ['stay_local', $this->t('Stay local'), $stay_local, $this->t("Don't go to a remote system when following a contact link.")],
|
||||
'$show_page_drop' => ['show_page_drop', $this->t('Show the post deletion checkbox'), $show_page_drop, $this->t("Display the checkbox for the post deletion on the network page.")],
|
||||
'$display_eventlist' => ['display_eventlist', $this->t('DIsplay the event list'), $display_eventlist, $this->t("Display the birthday reminder and event list on the network page.")],
|
||||
'$preview_mode' => ['preview_mode', $this->t('Link preview mode'), $preview_mode, $this->t('Appearance of the link preview that is added to each post with a link.'), $preview_modes, false],
|
||||
'$hide_empty_descriptions' => ['hide_empty_descriptions', $this->t('Hide pictures with empty alternative text'), $hide_empty_descriptions, $this->t("Don't display pictures that are missing the alternative text.")],
|
||||
'$hide_custom_emojis' => ['hide_custom_emojis', $this->t('Hide custom emojis'), $hide_custom_emojis, $this->t("Don't display custom emojis.")],
|
||||
'$platform_icon_style' => ['platform_icon_style', $this->t('Platform icons style'), $platform_icon_style, $this->t('Style of the platform icons'), $platform_icon_styles, false],
|
||||
|
||||
'$timeline_label' => $this->t('Label'),
|
||||
'$timeline_descriptiom' => $this->t('Description'),
|
||||
|
@ -330,7 +352,7 @@ class Display extends BaseSettings
|
|||
|
||||
'$channel_languages' => ['channel_languages[]', $this->t('Channel languages:'), $channel_languages, $this->t('Select all languages that you want to see in your channels.'), $languages, 'multiple'],
|
||||
|
||||
'$first_day_of_week' => ['first_day_of_week' , $this->t('Beginning of week:') , $first_day_of_week , '', $weekdays , false],
|
||||
'$first_day_of_week' => ['first_day_of_week', $this->t('Beginning of week:'), $first_day_of_week, '', $weekdays, false],
|
||||
'$calendar_default_view' => ['calendar_default_view', $this->t('Default calendar view:'), $calendar_default_view, '', $calendarViews, false],
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -77,7 +77,8 @@ class AppSpecific extends BaseSettings
|
|||
$this->baseUrl->redirect('settings/2fa/app_specific?t=' . self::getFormSecurityToken('settings_2fa_password'));
|
||||
} else {
|
||||
$this->appSpecificPassword = AppSpecificPassword::generateForUser($this->session->getLocalUserId(), $request['description'] ?? '');
|
||||
$this->systemMessages->addInfo($this->t('New app-specific password generated.'));
|
||||
$this->systemMessages->addInfo($this->t('New app-specific password generated: %s', $this->appSpecificPassword['plaintext_password']));
|
||||
$this->baseUrl->redirect('settings/2fa/app_specific?t=' . self::getFormSecurityToken('settings_2fa_password'));
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
|
@ -14,8 +14,11 @@ use Friendica\Core\Config\Capability\IManageConfigValues;
|
|||
use Friendica\Core\KeyValueStorage\Capability\IManageKeyValuePairs;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Core\Update;
|
||||
use Friendica\Core\Worker;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Database\DBStructure;
|
||||
use Friendica\Model\Register;
|
||||
use Friendica\Moderation\Entity\Report;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
@ -23,6 +26,10 @@ use Friendica\Util\Profiler;
|
|||
use Psr\Log\LoggerInterface;
|
||||
use Friendica\Network\HTTPException;
|
||||
|
||||
/**
|
||||
* Returns statistics of the current node for administration use
|
||||
* Like for monitoring
|
||||
*/
|
||||
class Stats extends BaseModule
|
||||
{
|
||||
/** @var IManageConfigValues */
|
||||
|
@ -129,7 +136,25 @@ class Stats extends BaseModule
|
|||
],
|
||||
'open' => $this->dba->count('report', ['status' => Report::STATUS_OPEN]),
|
||||
'closed' => $this->dba->count('report', ['status' => Report::STATUS_CLOSED]),
|
||||
]
|
||||
],
|
||||
'update' => [
|
||||
'available' => Update::isAvailable(),
|
||||
'available_version' => Update::getAvailableVersion(),
|
||||
'status' => Update::getStatus(),
|
||||
'db_status' => DBStructure::getUpdateStatus(),
|
||||
],
|
||||
'server' => [
|
||||
'version' => App::VERSION,
|
||||
'php' => [
|
||||
'version' => phpversion(),
|
||||
'upload_max_filesize' => ini_get('upload_max_filesize'),
|
||||
'post_max_size' => ini_get('post_max_size'),
|
||||
'memory_limit' => ini_get('memory_limit'),
|
||||
],
|
||||
'database' => [
|
||||
'max_allowed_packet' => DBA::getVariable('max_allowed_packet'),
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
if (Addon::isEnabled('bluesky')) {
|
||||
|
|
|
@ -33,14 +33,14 @@ class Xrd extends BaseModule
|
|||
}
|
||||
|
||||
$uri = urldecode(trim($_GET['uri']));
|
||||
$mode = self::getAcceptedContentType($_SERVER['HTTP_ACCEPT'] ?? '', Response::TYPE_JSON);
|
||||
$mode = self::getAcceptedContentType($_SERVER['HTTP_ACCEPT'] ?? '', Response::TYPE_XML);
|
||||
} else {
|
||||
if (empty($_GET['resource'])) {
|
||||
throw new BadRequestException();
|
||||
}
|
||||
|
||||
$uri = urldecode(trim($_GET['resource']));
|
||||
$mode = self::getAcceptedContentType($_SERVER['HTTP_ACCEPT'] ?? '', Response::TYPE_XML);
|
||||
$mode = self::getAcceptedContentType($_SERVER['HTTP_ACCEPT'] ?? '', Response::TYPE_JSON);
|
||||
}
|
||||
|
||||
if (Network::isValidHttpUrl($uri)) {
|
||||
|
|
|
@ -57,6 +57,9 @@ class FriendicaExtension extends BaseDataTransferObject
|
|||
*/
|
||||
protected $visibility;
|
||||
|
||||
/** @var string|null */
|
||||
protected $content;
|
||||
|
||||
/**
|
||||
* Creates a FriendicaExtension object
|
||||
*
|
||||
|
@ -86,7 +89,8 @@ class FriendicaExtension extends BaseDataTransferObject
|
|||
?string $version,
|
||||
?string $sitename,
|
||||
?FriendicaDeliveryData $delivery_data,
|
||||
?FriendicaVisibility $visibility
|
||||
?FriendicaVisibility $visibility,
|
||||
?string $content
|
||||
) {
|
||||
$this->title = $title;
|
||||
$this->changed_at = $changed_at ? DateTimeFormat::utc($changed_at, DateTimeFormat::JSON) : null;
|
||||
|
@ -100,6 +104,7 @@ class FriendicaExtension extends BaseDataTransferObject
|
|||
$this->version = $version;
|
||||
$this->sitename = $sitename;
|
||||
$this->visibility = $visibility;
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -121,7 +121,7 @@ class Status extends BaseDataTransferObject
|
|||
$this->entities = $entities;
|
||||
$this->extended_entities = $entities;
|
||||
|
||||
$origin = ContactSelector::networkToName($item['author-network'], $item['author-link'], $item['network']);
|
||||
$origin = ContactSelector::networkToName($item['author-network'], $item['network'], $item['author-gsid']);
|
||||
|
||||
if (empty($this->source)) {
|
||||
$this->source = $origin;
|
||||
|
|
|
@ -131,7 +131,7 @@ class User extends BaseDataTransferObject
|
|||
$this->name = $publicContact['name'] ?: $publicContact['nick'];
|
||||
$this->screen_name = $publicContact['nick'] ?: $publicContact['name'];
|
||||
$this->location = $publicContact['location'] ?:
|
||||
ContactSelector::networkToName($publicContact['network'], $publicContact['url'], $publicContact['protocol']);
|
||||
ContactSelector::networkToName($publicContact['network'], $publicContact['protocol'], $publicContact['gsid']);
|
||||
$this->derived = [];
|
||||
$this->url = $publicContact['url'];
|
||||
// No entities needed since we don't perform any shortening in the URL or description
|
||||
|
|
|
@ -606,8 +606,8 @@ class Post
|
|||
'edited' => $edited,
|
||||
'author_gsid' => $item['author-gsid'],
|
||||
'network' => $item['network'],
|
||||
'network_name' => ContactSelector::networkToName($item['author-network'], $item['author-link'], $item['network'], $item['author-gsid']),
|
||||
'network_icon' => ContactSelector::networkToIcon($item['network'], $item['author-link'], $item['author-gsid']),
|
||||
'network_name' => ContactSelector::networkToName($item['author-network'], $item['network'], $item['author-gsid']),
|
||||
'network_svg' => ContactSelector::networkToSVG($item['network'], $item['author-gsid'], '', DI::userSession()->getLocalUserId()),
|
||||
'received' => $item['received'],
|
||||
'commented' => $item['commented'],
|
||||
'created_date' => $item['created'],
|
||||
|
|
|
@ -877,10 +877,10 @@ class Processor
|
|||
{
|
||||
if (!empty($activity['mediatype']) && ($activity['mediatype'] == 'text/markdown')) {
|
||||
$item['title'] = strip_tags($activity['name'] ?? '');
|
||||
$content = Markdown::toBBCode($activity['content']);
|
||||
$content = Markdown::toBBCode($activity['content'] ?? '');
|
||||
} elseif (!empty($activity['mediatype']) && ($activity['mediatype'] == 'text/bbcode')) {
|
||||
$item['title'] = $activity['name'];
|
||||
$content = $activity['content'];
|
||||
$item['title'] = $activity['name'] ?? '';
|
||||
$content = $activity['content'] ?? '';
|
||||
} else {
|
||||
// By default assume "text/html"
|
||||
$item['title'] = HTML::toBBCode($activity['name'] ?? '');
|
||||
|
|
|
@ -637,14 +637,6 @@ class Transmitter
|
|||
$audience[] = $owner['url'];
|
||||
}
|
||||
|
||||
if (self::isAnnounce($item) || self::isAPPost($last_id)) {
|
||||
// Will be activated in a later step
|
||||
$networks = Protocol::FEDERATED;
|
||||
} else {
|
||||
// For now only send to these contacts:
|
||||
$networks = [Protocol::ACTIVITYPUB];
|
||||
}
|
||||
|
||||
$data = ['to' => [], 'cc' => [], 'bto' => [], 'bcc' => [], 'audience' => $audience];
|
||||
|
||||
if ($item['gravity'] == Item::GRAVITY_PARENT) {
|
||||
|
@ -704,7 +696,7 @@ class Transmitter
|
|||
$cid = Contact::getIdForURL($term['url'], $item['uid']);
|
||||
if (!empty($cid) && in_array($cid, $receiver_list)) {
|
||||
$contact = DBA::selectFirst('contact', ['url', 'network', 'protocol', 'gsid'], ['id' => $cid, 'network' => Protocol::FEDERATED]);
|
||||
if (!DBA::isResult($contact) || !self::isAPContact($contact, $networks)) {
|
||||
if (!DBA::isResult($contact)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -741,7 +733,7 @@ class Transmitter
|
|||
}
|
||||
|
||||
$contact = DBA::selectFirst('contact', ['url', 'hidden', 'network', 'protocol', 'gsid'], ['id' => $receiver, 'network' => Protocol::FEDERATED]);
|
||||
if (!DBA::isResult($contact) || !self::isAPContact($contact, $networks)) {
|
||||
if (!DBA::isResult($contact)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -984,44 +976,16 @@ class Transmitter
|
|||
return DBA::exists('inbox-status', ['url' => $url, 'archive' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given contact should be delivered via AP
|
||||
*
|
||||
* @param array $contact Contact array
|
||||
* @param array $networks Array with networks
|
||||
* @return bool Whether the used protocol matches ACTIVITYPUB
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function isAPContact(array $contact, array $networks): bool
|
||||
{
|
||||
if (in_array($contact['network'], $networks) || ($contact['protocol'] == Protocol::ACTIVITYPUB)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return GServer::getProtocol($contact['gsid'] ?? 0) == Post\DeliveryData::ACTIVITYPUB;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a list of inboxes of followers of a given user
|
||||
*
|
||||
* @param integer $uid User ID
|
||||
* @param boolean $all_ap Retrieve all AP enabled inboxes
|
||||
* @return array of follower inboxes
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function fetchTargetInboxesforUser(int $uid, bool $all_ap = false): array
|
||||
public static function fetchTargetInboxesforUser(int $uid): array
|
||||
{
|
||||
$inboxes = [];
|
||||
|
||||
if ($all_ap) {
|
||||
// Will be activated in a later step
|
||||
$networks = Protocol::FEDERATED;
|
||||
} else {
|
||||
// For now only send to these contacts:
|
||||
$networks = [Protocol::ACTIVITYPUB];
|
||||
}
|
||||
|
||||
$condition = [
|
||||
'uid' => $uid,
|
||||
'self' => false,
|
||||
|
@ -1029,37 +993,51 @@ class Transmitter
|
|||
'pending' => false,
|
||||
'blocked' => false,
|
||||
'network' => Protocol::FEDERATED,
|
||||
'contact-type' => [Contact::TYPE_UNKNOWN, Contact::TYPE_PERSON, Contact::TYPE_NEWS, Contact::TYPE_ORGANISATION],
|
||||
];
|
||||
|
||||
if (!empty($uid)) {
|
||||
$condition['rel'] = [Contact::FOLLOWER, Contact::FRIEND];
|
||||
}
|
||||
|
||||
$contacts = DBA::select('contact', ['id', 'url', 'network', 'protocol', 'gsid'], $condition);
|
||||
while ($contact = DBA::fetch($contacts)) {
|
||||
if (!self::isAPContact($contact, $networks)) {
|
||||
return self::addInboxesForCondition($condition, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch inboxes for a list of contacts
|
||||
*
|
||||
* @param array $recipients
|
||||
* @param array $inboxes
|
||||
* @return array
|
||||
*/
|
||||
public static function addInboxesForRecipients(array $recipients, array $inboxes): array
|
||||
{
|
||||
return self::addInboxesForCondition(['id' => $recipients], $inboxes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of inboxes for a given contact condition
|
||||
*
|
||||
* @param array $condition
|
||||
* @param array $inboxes
|
||||
* @return array
|
||||
*/
|
||||
private static function addInboxesForCondition(array $condition, array $inboxes): array
|
||||
{
|
||||
$condition = DBA::mergeConditions($condition, ["(`ap-inbox` IS NOT NULL OR `ap-sharedinbox` IS NOT NULL)"]);
|
||||
|
||||
$accounts = DBA::select('account-user-view', ['id', 'url', 'ap-inbox', 'ap-sharedinbox'], $condition);
|
||||
while ($account = DBA::fetch($accounts)) {
|
||||
if (!empty($account['ap-sharedinbox']) && !Contact::isLocal($account['url'])) {
|
||||
$target = $account['ap-sharedinbox'];
|
||||
} elseif (!empty($account['ap-inbox'])) {
|
||||
$target = $account['ap-inbox'];
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Network::isUrlBlocked($contact['url'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$profile = APContact::getByURL($contact['url'], false);
|
||||
if (!empty($profile)) {
|
||||
if (empty($profile['sharedinbox']) || Contact::isLocal($contact['url'])) {
|
||||
$target = $profile['inbox'];
|
||||
} else {
|
||||
$target = $profile['sharedinbox'];
|
||||
}
|
||||
if (!self::archivedInbox($target)) {
|
||||
$inboxes[$target][] = $contact['id'];
|
||||
}
|
||||
if (!Transmitter::archivedInbox($target) && (empty($inboxes[$target]) || !in_array($account['id'], $inboxes[$target]))) {
|
||||
$inboxes[$target][] = $account['id'];
|
||||
}
|
||||
}
|
||||
DBA::close($contacts);
|
||||
|
||||
return $inboxes;
|
||||
}
|
||||
|
||||
|
@ -1106,7 +1084,7 @@ class Transmitter
|
|||
}
|
||||
|
||||
if ($item_profile && ($receiver == $item_profile['followers']) && ($uid == $profile_uid)) {
|
||||
$inboxes = array_merge_recursive($inboxes, self::fetchTargetInboxesforUser($uid, true));
|
||||
$inboxes = array_merge_recursive($inboxes, self::fetchTargetInboxesforUser($uid));
|
||||
} else {
|
||||
$profile = APContact::getByURL($receiver, false);
|
||||
if (!empty($profile)) {
|
||||
|
@ -1413,7 +1391,7 @@ class Transmitter
|
|||
}
|
||||
}
|
||||
|
||||
if (!$api_mode && !$item['origin']) {
|
||||
if (!$api_mode && !$item['deleted'] && !$item['origin']) {
|
||||
Logger::debug('Post is not ours and is not stored', ['id' => $item['id'], 'uri-id' => $item['uri-id']]);
|
||||
return false;
|
||||
}
|
||||
|
@ -2103,17 +2081,18 @@ class Transmitter
|
|||
* Creates an activity id for a given contact id
|
||||
*
|
||||
* @param integer $cid Contact ID of target
|
||||
* @param integer $uid Optional user id. if empty, the contact uid is used.
|
||||
*
|
||||
* @return bool|string activity id
|
||||
*/
|
||||
public static function activityIDFromContact(int $cid)
|
||||
public static function activityIDFromContact(int $cid, int $uid = 0)
|
||||
{
|
||||
$contact = DBA::selectFirst('contact', ['uid', 'id', 'created'], ['id' => $cid]);
|
||||
if (!DBA::isResult($contact)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$hash = hash('ripemd128', $contact['uid'] . '-' . $contact['id'] . '-' . $contact['created']);
|
||||
$hash = hash('ripemd128', $uid ?: $contact['uid'] . '-' . $contact['id'] . '-' . $contact['created']);
|
||||
$uuid = substr($hash, 0, 8) . '-' . substr($hash, 8, 4) . '-' . substr($hash, 12, 4) . '-' . substr($hash, 16, 4) . '-' . substr($hash, 20, 12);
|
||||
return DI::baseUrl() . '/activity/' . $uuid;
|
||||
}
|
||||
|
@ -2477,6 +2456,53 @@ class Transmitter
|
|||
return HTTPSignature::transmit($signed, $profile['inbox'], $owner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transmits a message that we don't want to block this contact anymore
|
||||
*
|
||||
* @param string $target Target profile
|
||||
* @param integer $cid Contact id
|
||||
* @param array $owner Sender owner-view record
|
||||
* @return bool success
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function sendContactUnblock(string $target, int $cid, array $owner): bool
|
||||
{
|
||||
$profile = APContact::getByURL($target);
|
||||
if (empty($profile['inbox'])) {
|
||||
Logger::warning('No inbox found for target', ['target' => $target, 'profile' => $profile]);
|
||||
return false;
|
||||
}
|
||||
|
||||
$object_id = self::activityIDFromContact($cid, $owner['uid']);
|
||||
if (empty($object_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$objectId = DI::baseUrl() . '/activity/' . System::createGUID();
|
||||
|
||||
$data = [
|
||||
'@context' => ActivityPub::CONTEXT,
|
||||
'id' => $objectId,
|
||||
'type' => 'Undo',
|
||||
'actor' => $owner['url'],
|
||||
'object' => [
|
||||
'id' => $object_id,
|
||||
'type' => 'Block',
|
||||
'actor' => $owner['url'],
|
||||
'object' => $profile['url']
|
||||
],
|
||||
'instrument' => self::getService(),
|
||||
'to' => [$profile['url']],
|
||||
];
|
||||
|
||||
Logger::info('Sending undo to ' . $target . ' for user ' . $owner['uid'] . ' with id ' . $objectId);
|
||||
|
||||
$signed = LDSignature::sign($data, $owner);
|
||||
return HTTPSignature::transmit($signed, $profile['inbox'], $owner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepends mentions (@) to $body variable
|
||||
*
|
||||
|
|
|
@ -986,7 +986,7 @@ class DFRN
|
|||
Logger::notice('Got exception', ['code' => $th->getCode(), 'message' => $th->getMessage()]);
|
||||
return -25;
|
||||
}
|
||||
Item::incrementOutbound(Protocol::DFRN);
|
||||
|
||||
$xml = $postResult->getBodyString();
|
||||
|
||||
$curl_stat = $postResult->getReturnCode();
|
||||
|
@ -1017,6 +1017,7 @@ class DFRN
|
|||
|
||||
if (!empty($contact['gsid'])) {
|
||||
GServer::setReachableById($contact['gsid'], Protocol::DFRN);
|
||||
Item::incrementOutbound(Protocol::DFRN);
|
||||
}
|
||||
|
||||
if (!empty($res->message)) {
|
||||
|
|
|
@ -2962,7 +2962,6 @@ class Diaspora
|
|||
return 0;
|
||||
}
|
||||
$return_code = $postResult->getReturnCode();
|
||||
Item::incrementOutbound(Protocol::DIASPORA);
|
||||
} else {
|
||||
Logger::notice('test_mode');
|
||||
return 200;
|
||||
|
@ -2971,6 +2970,7 @@ class Diaspora
|
|||
if (!empty($contact['gsid']) && (empty($return_code) || $postResult->isTimeout())) {
|
||||
GServer::setFailureById($contact['gsid']);
|
||||
} elseif (!empty($contact['gsid']) && ($return_code >= 200) && ($return_code <= 299)) {
|
||||
Item::incrementOutbound(Protocol::DIASPORA);
|
||||
GServer::setReachableById($contact['gsid'], Protocol::DIASPORA);
|
||||
}
|
||||
|
||||
|
|
|
@ -129,8 +129,10 @@ class OAuth
|
|||
return [];
|
||||
}
|
||||
|
||||
// The redirect_uri could contain several URI that are separated by spaces.
|
||||
if (($application['redirect_uri'] != $redirect_uri) && !in_array($redirect_uri, explode(' ', $application['redirect_uri']))) {
|
||||
// The redirect_uri could contain several URI that are separated by spaces or new lines.
|
||||
$uris = explode(' ', str_replace(["\n", "\r", "\t"], ' ', $application['redirect_uri']));
|
||||
if (!in_array($redirect_uri, $uris)) {
|
||||
Logger::warning('Redirection uri does not match', ['redirect_uri' => $redirect_uri, 'application-redirect_uri' => $application['redirect_uri']]);
|
||||
return [];
|
||||
}
|
||||
|
||||
|
|
|
@ -297,7 +297,9 @@ class HTTPSignature
|
|||
|
||||
self::setInboxStatus($target, ($return_code >= 200) && ($return_code <= 299));
|
||||
|
||||
Item::incrementOutbound(Protocol::ACTIVITYPUB);
|
||||
if (($return_code >= 200) && ($return_code <= 299)) {
|
||||
Item::incrementOutbound(Protocol::ACTIVITYPUB);
|
||||
}
|
||||
|
||||
return $postResult;
|
||||
}
|
||||
|
@ -757,9 +759,9 @@ class HTTPSignature
|
|||
}
|
||||
|
||||
if (in_array('(expires)', $sig_block['headers']) && !empty($sig_block['expires'])) {
|
||||
$expired = min($sig_block['expires'], $created + 300);
|
||||
$expired = min($sig_block['expires'], $created + 3600);
|
||||
} else {
|
||||
$expired = $created + 300;
|
||||
$expired = $created + 3600;
|
||||
}
|
||||
|
||||
// Check if the signed date field is in an acceptable range
|
||||
|
|
|
@ -58,6 +58,9 @@ class JsonLD
|
|||
case 'http://joinmastodon.org/ns':
|
||||
$url = DI::basePath() . '/static/joinmastodon.jsonld';
|
||||
break;
|
||||
case 'https://purl.archive.org/socialweb/webfinger':
|
||||
$url = DI::basePath() . '/static/socialweb-webfinger.jsonld';
|
||||
break;
|
||||
default:
|
||||
switch (parse_url($url, PHP_URL_PATH)) {
|
||||
case '/schemas/litepub-0.1.jsonld';
|
||||
|
|
|
@ -155,7 +155,8 @@ class Strings
|
|||
{
|
||||
if ($network != '') {
|
||||
if ($url != '') {
|
||||
$network_name = '<a href="' . $url . '">' . ContactSelector::networkToName($network, $url) . '</a>';
|
||||
$gsid = ContactSelector::getServerIdForProfile($url);
|
||||
$network_name = '<a href="' . $url . '">' . ContactSelector::networkToName($network, '', $gsid) . '</a>';
|
||||
} else {
|
||||
$network_name = ContactSelector::networkToName($network);
|
||||
}
|
||||
|
|
38
src/Worker/Contact/Block.php
Normal file
38
src/Worker/Contact/Block.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
// Copyright (C) 2010-2024, the Friendica project
|
||||
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
namespace Friendica\Worker\Contact;
|
||||
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Core\Worker;
|
||||
use Friendica\Model\Contact;
|
||||
|
||||
class Block
|
||||
{
|
||||
const WORKER_DEFER_LIMIT = 5;
|
||||
|
||||
/**
|
||||
* Issue asynchronous block message to remote servers.
|
||||
*
|
||||
* @param int $cid Target public contact (uid = 0) id
|
||||
* @param int $uid Source local user id
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function execute(int $cid, int $uid)
|
||||
{
|
||||
$contact = Contact::getById($cid);
|
||||
if (empty($contact)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$result = Protocol::block($contact, $uid);
|
||||
if ($result === false) {
|
||||
Worker::defer(self::WORKER_DEFER_LIMIT);
|
||||
}
|
||||
}
|
||||
}
|
38
src/Worker/Contact/Unblock.php
Normal file
38
src/Worker/Contact/Unblock.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
// Copyright (C) 2010-2024, the Friendica project
|
||||
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
namespace Friendica\Worker\Contact;
|
||||
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Core\Worker;
|
||||
use Friendica\Model\Contact;
|
||||
|
||||
class Unblock
|
||||
{
|
||||
const WORKER_DEFER_LIMIT = 5;
|
||||
|
||||
/**
|
||||
* Issue asynchronous unblock message to remote servers.
|
||||
*
|
||||
* @param int $cid Target public contact (uid = 0) id
|
||||
* @param int $uid Source local user id
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function execute(int $cid, int $uid)
|
||||
{
|
||||
$contact = Contact::getById($cid);
|
||||
if (empty($contact)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$result = Protocol::unblock($contact, $uid);
|
||||
if ($result === false) {
|
||||
Worker::defer(self::WORKER_DEFER_LIMIT);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ use Friendica\Model\Tag;
|
|||
use Friendica\Model\User;
|
||||
use Friendica\Protocol\Activity;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Protocol\ActivityPub\Transmitter;
|
||||
use Friendica\Protocol\Diaspora;
|
||||
use Friendica\Protocol\Delivery;
|
||||
use Friendica\Util\LDSignature;
|
||||
|
@ -46,7 +47,6 @@ class Notifier
|
|||
Logger::info('Invoked', ['cmd' => $cmd, 'target' => $post_uriid, 'sender_uid' => $sender_uid]);
|
||||
|
||||
$target_id = $post_uriid;
|
||||
$top_level = false;
|
||||
$recipients = [];
|
||||
|
||||
$delivery_contacts_stmt = null;
|
||||
|
@ -126,8 +126,6 @@ class Notifier
|
|||
$item['deleted'] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
$top_level = $target_item['gravity'] == Item::GRAVITY_PARENT;
|
||||
}
|
||||
|
||||
$owner = User::getOwnerDataById($uid);
|
||||
|
@ -161,16 +159,17 @@ class Notifier
|
|||
|
||||
Logger::info('Got post', ['guid' => $target_item['guid'], 'uri-id' => $target_item['uri-id'], 'network' => $target_item['network'], 'parent-network' => $parent['network'], 'thread-parent-network' => $thr_parent['network']]);
|
||||
|
||||
if (!self::isRemovalActivity($cmd, $owner, Protocol::ACTIVITYPUB)) {
|
||||
$apdelivery = self::activityPubDelivery($cmd, $target_item, $parent, $thr_parent, $a->getQueueValue('priority'), $a->getQueueValue('created'), $owner);
|
||||
$ap_contacts = $apdelivery['contacts'];
|
||||
$delivery_queue_count += $apdelivery['count'];
|
||||
// Restrict distribution to AP, when there are no permissions.
|
||||
if (($target_item['private'] == Item::PRIVATE) && empty($target_item['allow_cid']) && empty($target_item['allow_gid']) && empty($target_item['deny_cid']) && empty($target_item['deny_gid'])) {
|
||||
$only_ap_delivery = true;
|
||||
$public_message = false;
|
||||
$diaspora_delivery = false;
|
||||
}
|
||||
// Restrict distribution to AP, when there are no permissions.
|
||||
if (!self::isRemovalActivity($cmd, $owner, Protocol::ACTIVITYPUB) && ($target_item['private'] == Item::PRIVATE) && empty($target_item['allow_cid']) && empty($target_item['allow_gid']) && empty($target_item['deny_cid']) && empty($target_item['deny_gid'])) {
|
||||
$only_ap_delivery = true;
|
||||
$public_message = false;
|
||||
$diaspora_delivery = false;
|
||||
}
|
||||
|
||||
if (!$target_item['origin'] && $target_item['network'] == Protocol::ACTIVITYPUB) {
|
||||
$only_ap_delivery = true;
|
||||
$diaspora_delivery = false;
|
||||
Logger::debug('Remote post arrived via AP', ['guid' => $target_item['guid'], 'uri-id' => $target_item['uri-id'], 'network' => $target_item['network'], 'parent-network' => $parent['network'], 'thread-parent-network' => $thr_parent['network']]);
|
||||
}
|
||||
|
||||
// Only deliver threaded replies (comment to a comment) to Diaspora
|
||||
|
@ -194,45 +193,19 @@ class Notifier
|
|||
|
||||
// if $parent['wall'] == 1 we will already have the parent message in our array
|
||||
// and we will relay the whole lot.
|
||||
|
||||
$localhost = str_replace('www.','', DI::baseUrl()->getHost());
|
||||
if (strpos($localhost,':')) {
|
||||
$localhost = substr($localhost,0,strpos($localhost,':'));
|
||||
}
|
||||
/**
|
||||
*
|
||||
* Be VERY CAREFUL if you make any changes to the following several lines. Seemingly innocuous changes
|
||||
* have been known to cause runaway conditions which affected several servers, along with
|
||||
* permissions issues.
|
||||
*
|
||||
*/
|
||||
|
||||
$relay_to_owner = false;
|
||||
|
||||
if (!$top_level && ($parent['wall'] == 0) && (stristr($target_item['uri'],$localhost))) {
|
||||
if (($target_item['gravity'] != Item::GRAVITY_PARENT) && !$parent['wall'] && $target_item['origin']) {
|
||||
$relay_to_owner = true;
|
||||
}
|
||||
|
||||
// until the 'origin' flag has been in use for several months
|
||||
// we will just use it as a fallback test
|
||||
// later we will be able to use it as the primary test of whether or not to relay.
|
||||
|
||||
if (!$target_item['origin']) {
|
||||
$relay_to_owner = false;
|
||||
}
|
||||
if ($parent['origin']) {
|
||||
if (!$target_item['origin'] || $parent['origin']) {
|
||||
$relay_to_owner = false;
|
||||
}
|
||||
|
||||
// Special treatment for group posts
|
||||
if (Item::isGroupPost($target_item['uri-id'])) {
|
||||
$relay_to_owner = true;
|
||||
$direct_group_delivery = true;
|
||||
}
|
||||
|
||||
// Avoid that comments in a group thread are sent to OStatus
|
||||
if (Item::isGroupPost($parent['uri-id'])) {
|
||||
$direct_group_delivery = true;
|
||||
}
|
||||
|
||||
$exclusive_delivery = false;
|
||||
|
@ -279,17 +252,19 @@ class Notifier
|
|||
return;
|
||||
}
|
||||
|
||||
if (strlen($parent['allow_cid'])
|
||||
if (
|
||||
strlen($parent['allow_cid'])
|
||||
|| strlen($parent['allow_gid'])
|
||||
|| strlen($parent['deny_cid'])
|
||||
|| strlen($parent['deny_gid'])) {
|
||||
|| strlen($parent['deny_gid'])
|
||||
) {
|
||||
$public_message = false; // private recipients, not public
|
||||
}
|
||||
|
||||
$aclFormatter = DI::aclFormatter();
|
||||
|
||||
$allow_people = $aclFormatter->expand($parent['allow_cid']);
|
||||
$allow_circles = Circle::expand($uid, $aclFormatter->expand($parent['allow_gid']),true);
|
||||
$allow_circles = Circle::expand($uid, $aclFormatter->expand($parent['allow_gid']), true);
|
||||
$deny_people = $aclFormatter->expand($parent['deny_cid']);
|
||||
$deny_circles = Circle::expand($uid, $aclFormatter->expand($parent['deny_gid']));
|
||||
|
||||
|
@ -297,10 +272,10 @@ class Notifier
|
|||
$recipients[] = $item['contact-id'];
|
||||
// pull out additional tagged people to notify (if public message)
|
||||
if ($public_message && $item['inform']) {
|
||||
$people = explode(',',$item['inform']);
|
||||
$people = explode(',', $item['inform']);
|
||||
foreach ($people as $person) {
|
||||
if (substr($person,0,4) === 'cid:') {
|
||||
$recipients[] = intval(substr($person,4));
|
||||
if (substr($person, 0, 4) === 'cid:') {
|
||||
$recipients[] = intval(substr($person, 4));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -313,7 +288,7 @@ class Notifier
|
|||
// If this is a public message and pubmail is set on the parent, include all your email contacts
|
||||
if (
|
||||
function_exists('imap_open')
|
||||
&& !DI::config()->get('system','imap_disabled')
|
||||
&& !DI::config()->get('system', 'imap_disabled')
|
||||
&& $public_message
|
||||
&& intval($target_item['pubmail'])
|
||||
) {
|
||||
|
@ -338,51 +313,65 @@ class Notifier
|
|||
$public_message = false;
|
||||
}
|
||||
|
||||
if (empty($delivery_contacts_stmt)) {
|
||||
if ($only_ap_delivery) {
|
||||
$recipients = $ap_contacts;
|
||||
} elseif ($followup) {
|
||||
$recipients = $recipients_followup;
|
||||
}
|
||||
$condition = ['id' => $recipients, 'self' => false, 'uid' => [0, $uid],
|
||||
'blocked' => false, 'pending' => false, 'archive' => false];
|
||||
if (!empty($networks)) {
|
||||
$condition['network'] = $networks;
|
||||
}
|
||||
$delivery_contacts_stmt = DBA::select('contact', ['id', 'uri-id', 'addr', 'url', 'network', 'protocol', 'baseurl', 'gsid', 'batch'], $condition);
|
||||
if ($only_ap_delivery) {
|
||||
$recipients = [];
|
||||
} elseif ($followup) {
|
||||
$recipients = $recipients_followup;
|
||||
}
|
||||
|
||||
$conversants = [];
|
||||
$batch_delivery = false;
|
||||
$apdelivery = self::activityPubDelivery($cmd, $target_item, $parent, $thr_parent, $a->getQueueValue('priority'), $a->getQueueValue('created'), $recipients);
|
||||
$ap_contacts = $apdelivery['contacts'];
|
||||
$delivery_queue_count += $apdelivery['count'];
|
||||
|
||||
if ($public_message && !in_array($cmd, [Delivery::MAIL, Delivery::SUGGESTION]) && !$followup) {
|
||||
$participants = [];
|
||||
|
||||
if ($diaspora_delivery && !$unlisted) {
|
||||
$batch_delivery = true;
|
||||
|
||||
$participants = DBA::selectToArray('contact', ['batch', 'network', 'protocol', 'baseurl', 'gsid', 'id', 'url', 'name'],
|
||||
["`network` = ? AND `batch` != '' AND `uid` = ? AND `rel` != ? AND NOT `blocked` AND NOT `pending` AND NOT `archive`", Protocol::DIASPORA, $owner['uid'], Contact::SHARING],
|
||||
['group_by' => ['batch', 'network', 'protocol']]);
|
||||
|
||||
// Fetch the participation list
|
||||
// The function will ensure that there are no duplicates
|
||||
$participants = Diaspora::participantsForThread($target_item, $participants);
|
||||
if (!$only_ap_delivery) {
|
||||
if (empty($delivery_contacts_stmt)) {
|
||||
$condition = ['id' => $recipients, 'self' => false, 'uid' => [0, $uid],
|
||||
'blocked' => false, 'pending' => false, 'archive' => false];
|
||||
if (!empty($networks)) {
|
||||
$condition['network'] = $networks;
|
||||
}
|
||||
$delivery_contacts_stmt = DBA::select('contact', ['id', 'uri-id', 'addr', 'url', 'network', 'protocol', 'baseurl', 'gsid', 'batch'], $condition);
|
||||
}
|
||||
|
||||
$condition = ['network' => Protocol::DFRN, 'uid' => $owner['uid'], 'blocked' => false,
|
||||
'pending' => false, 'archive' => false, 'rel' => [Contact::FOLLOWER, Contact::FRIEND]];
|
||||
$conversants = [];
|
||||
$batch_delivery = false;
|
||||
|
||||
$contacts = DBA::selectToArray('contact', ['id', 'uri-id', 'url', 'addr', 'name', 'network', 'protocol', 'baseurl', 'gsid'], $condition);
|
||||
if ($public_message && !in_array($cmd, [Delivery::MAIL, Delivery::SUGGESTION]) && !$followup) {
|
||||
$participants = [];
|
||||
|
||||
$conversants = array_merge($contacts, $participants);
|
||||
if ($diaspora_delivery && !$unlisted) {
|
||||
$batch_delivery = true;
|
||||
|
||||
$delivery_queue_count += self::delivery($cmd, $post_uriid, $sender_uid, $target_item, $thr_parent, $owner, $batch_delivery, true, $conversants, $ap_contacts, []);
|
||||
$participants = DBA::selectToArray('contact', ['batch', 'network', 'protocol', 'baseurl', 'gsid', 'id', 'url', 'name'],
|
||||
["`network` = ? AND `batch` != '' AND `uid` = ? AND `rel` != ? AND NOT `blocked` AND NOT `pending` AND NOT `archive`", Protocol::DIASPORA, $owner['uid'], Contact::SHARING],
|
||||
['group_by' => ['batch', 'network', 'protocol']]);
|
||||
|
||||
// Fetch the participation list
|
||||
// The function will ensure that there are no duplicates
|
||||
$participants = Diaspora::participantsForThread($target_item, $participants);
|
||||
}
|
||||
|
||||
$condition = [
|
||||
'network' => Protocol::DFRN,
|
||||
'uid' => $owner['uid'],
|
||||
'self' => false,
|
||||
'blocked' => false,
|
||||
'pending' => false,
|
||||
'archive' => false,
|
||||
'rel' => [Contact::FOLLOWER, Contact::FRIEND]
|
||||
];
|
||||
|
||||
$contacts = DBA::selectToArray('contact', ['id', 'uri-id', 'url', 'addr', 'name', 'network', 'protocol', 'baseurl', 'gsid'], $condition);
|
||||
|
||||
$conversants = array_merge($contacts, $participants);
|
||||
|
||||
$delivery_queue_count += self::delivery($cmd, $post_uriid, $sender_uid, $target_item, $parent, $thr_parent, $owner, $batch_delivery, true, $conversants, $ap_contacts, []);
|
||||
}
|
||||
|
||||
$contacts = DBA::toArray($delivery_contacts_stmt);
|
||||
$delivery_queue_count += self::delivery($cmd, $post_uriid, $sender_uid, $target_item, $parent, $thr_parent, $owner, $batch_delivery, false, $contacts, $ap_contacts, $conversants);
|
||||
}
|
||||
|
||||
$contacts = DBA::toArray($delivery_contacts_stmt);
|
||||
$delivery_queue_count += self::delivery($cmd, $post_uriid, $sender_uid, $target_item, $thr_parent, $owner, $batch_delivery, false, $contacts, $ap_contacts, $conversants);
|
||||
|
||||
if (!empty($target_item)) {
|
||||
Logger::info('Calling hooks for ' . $cmd . ' ' . $target_id);
|
||||
|
||||
|
@ -411,6 +400,7 @@ class Notifier
|
|||
* @param int $post_uriid
|
||||
* @param int $sender_uid
|
||||
* @param array $target_item
|
||||
* @param array $parent
|
||||
* @param array $thr_parent
|
||||
* @param array $owner
|
||||
* @param bool $batch_delivery
|
||||
|
@ -422,7 +412,7 @@ class Notifier
|
|||
* @throws InternalServerErrorException
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function delivery(string $cmd, int $post_uriid, int $sender_uid, array $target_item, array $thr_parent, array $owner, bool $batch_delivery, bool $in_batch, array $contacts, array $ap_contacts, array $conversants = []): int
|
||||
private static function delivery(string $cmd, int $post_uriid, int $sender_uid, array $target_item, array $parent, array $thr_parent, array $owner, bool $batch_delivery, bool $in_batch, array $contacts, array $ap_contacts, array $conversants = []): int
|
||||
{
|
||||
$a = DI::app();
|
||||
$delivery_queue_count = 0;
|
||||
|
@ -433,6 +423,13 @@ class Notifier
|
|||
}
|
||||
|
||||
foreach ($contacts as $contact) {
|
||||
// Transmit via Diaspora if the thread had started as Diaspora post.
|
||||
// Also transmit via Diaspora if this is a direct answer to a Diaspora comment.
|
||||
if (($contact['network'] != Protocol::DIASPORA) && in_array(Protocol::DIASPORA, [$parent['network'] ?? '', $thr_parent['network'] ?? '', $target_item['network'] ?? ''])) {
|
||||
Logger::info('Enforcing the Diaspora protocol', ['id' => $contact['id'], 'network' => $contact['network'], 'parent' => $parent['network'], 'thread-parent' => $thr_parent['network'], 'post' => $target_item['network']]);
|
||||
$contact['network'] = Protocol::DIASPORA;
|
||||
}
|
||||
|
||||
// Direct delivery of local contacts
|
||||
if (!in_array($cmd, [Delivery::RELOCATION, Delivery::SUGGESTION, Delivery::MAIL]) && $target_uid = User::getIdForURL($contact['url'])) {
|
||||
if ($cmd == Delivery::DELETION) {
|
||||
|
@ -453,14 +450,19 @@ class Notifier
|
|||
continue;
|
||||
}
|
||||
|
||||
// Deletions are always sent via DFRN as well.
|
||||
// This is done until we can perform deletions of foreign comments on our own threads via AP.
|
||||
if (($cmd != Delivery::DELETION) && in_array($contact['id'], $ap_contacts)) {
|
||||
Logger::info('Contact is already delivered via AP, so skip delivery via legacy DFRN/Diaspora', ['target' => $post_uriid, 'uid' => $sender_uid, 'contact' => $contact['url']]);
|
||||
$cdata = Contact::getPublicAndUserContactID($contact['id'], $sender_uid);
|
||||
if (in_array($cdata['public'] ?: $contact['id'], $ap_contacts)) {
|
||||
Logger::info('The public contact is already delivered via AP, so skip delivery via legacy DFRN/Diaspora', ['batch' => $in_batch, 'target' => $post_uriid, 'uid' => $sender_uid, 'contact' => $contact['url']]);
|
||||
continue;
|
||||
} elseif (in_array($cdata['user'] ?: $contact['id'], $ap_contacts)) {
|
||||
Logger::info('The user contact is already delivered via AP, so skip delivery via legacy DFRN/Diaspora', ['batch' => $in_batch, 'target' => $post_uriid, 'uid' => $sender_uid, 'contact' => $contact['url']]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!empty($contact['id']) && Contact::isArchived($contact['id'])) {
|
||||
// We mark the contact here, since we could have only got here, when the "archived" value on this
|
||||
// specific contact hadn't been set.
|
||||
Contact::markForArchival($contact);
|
||||
Logger::info('Contact is archived, so skip delivery', ['target' => $post_uriid, 'uid' => $sender_uid, 'contact' => $contact['url']]);
|
||||
continue;
|
||||
}
|
||||
|
@ -505,7 +507,7 @@ class Notifier
|
|||
continue;
|
||||
}
|
||||
|
||||
Logger::info('Delivery', ['batch' => $in_batch, 'target' => $post_uriid, 'uid' => $sender_uid, 'guid' => $target_item['guid'] ?? '', 'to' => $contact]);
|
||||
Logger::info('Delivery', ['cmd' => $cmd, 'batch' => $in_batch, 'target' => $post_uriid, 'uid' => $sender_uid, 'guid' => $target_item['guid'] ?? '', 'to' => $contact]);
|
||||
|
||||
// Ensure that posts with our own protocol arrives before Diaspora posts arrive.
|
||||
// Situation is that sometimes Friendica servers receive Friendica posts over the Diaspora protocol first.
|
||||
|
@ -578,7 +580,7 @@ class Notifier
|
|||
*/
|
||||
private static function isRemovalActivity(string $cmd, array $owner, string $network): bool
|
||||
{
|
||||
return ($cmd == Delivery::DELETION) && $owner['account_removed'] && in_array($network, [Protocol::ACTIVITYPUB, Protocol::DIASPORA]);
|
||||
return ($cmd == Delivery::REMOVAL) && $owner['account_removed'] && in_array($network, [Protocol::ACTIVITYPUB, Protocol::DIASPORA]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -602,7 +604,7 @@ class Notifier
|
|||
return false;
|
||||
}
|
||||
|
||||
while($contact = DBA::fetch($contacts_stmt)) {
|
||||
while ($contact = DBA::fetch($contacts_stmt)) {
|
||||
Contact::terminateFriendship($contact);
|
||||
}
|
||||
DBA::close($contacts_stmt);
|
||||
|
@ -623,15 +625,16 @@ class Notifier
|
|||
* @param array $target_item
|
||||
* @param array $parent
|
||||
* @param array $thr_parent
|
||||
* @param int $priority The priority the Notifier queue item was created with
|
||||
* @param string $created The date the Notifier queue item was created on
|
||||
* @param int $priority The priority the Notifier queue item was created with
|
||||
* @param string $created The date the Notifier queue item was created on
|
||||
* @param array $recipients Array of receivers
|
||||
*
|
||||
* @return array 'count' => The number of delivery tasks created, 'contacts' => their contact ids
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
* @todo Unused parameter $owner
|
||||
*/
|
||||
private static function activityPubDelivery($cmd, array $target_item, array $parent, array $thr_parent, int $priority, string $created, $owner): array
|
||||
private static function activityPubDelivery($cmd, array $target_item, array $parent, array $thr_parent, int $priority, string $created, array $recipients): array
|
||||
{
|
||||
// Don't deliver via AP when the starting post isn't from a federated network
|
||||
if (!in_array($parent['network'], Protocol::FEDERATED)) {
|
||||
|
@ -680,10 +683,10 @@ class Notifier
|
|||
|
||||
Logger::info('Origin item will be distributed', ['id' => $target_item['id'], 'url' => $target_item['uri'], 'verb' => $target_item['verb']]);
|
||||
$check_signature = false;
|
||||
} elseif (!Post\Activity::exists($target_item['uri-id'])) {
|
||||
Logger::info('Remote item is no AP post. It will not be distributed.', ['id' => $target_item['id'], 'url' => $target_item['uri'], 'verb' => $target_item['verb']]);
|
||||
} elseif (!$target_item['deleted'] && !Post\Activity::exists($target_item['uri-id'])) {
|
||||
Logger::info('Remote activity not found. It will not be distributed.', ['id' => $target_item['id'], 'url' => $target_item['uri'], 'verb' => $target_item['verb']]);
|
||||
return ['count' => 0, 'contacts' => []];
|
||||
} elseif ($parent['origin'] && (($target_item['gravity'] != Item::GRAVITY_ACTIVITY) || DI::config()->get('system', 'redistribute_activities'))) {
|
||||
} elseif ($parent['origin'] && ($target_item['private'] != Item::PRIVATE) && (($target_item['gravity'] != Item::GRAVITY_ACTIVITY) || DI::config()->get('system', 'redistribute_activities'))) {
|
||||
$inboxes = ActivityPub\Transmitter::fetchTargetInboxes($parent, $uid);
|
||||
|
||||
if (in_array($target_item['private'], [Item::PUBLIC])) {
|
||||
|
@ -697,6 +700,10 @@ class Notifier
|
|||
return ['count' => 0, 'contacts' => []];
|
||||
}
|
||||
|
||||
if ($target_item['private'] != Item::PRIVATE) {
|
||||
$inboxes = Transmitter::addInboxesForRecipients($recipients, $inboxes);
|
||||
}
|
||||
|
||||
if (empty($inboxes) && empty($relay_inboxes)) {
|
||||
Logger::info('No inboxes found for item ' . $target_item['id'] . ' with URL ' . $target_item['uri'] . '. It will not be distributed.');
|
||||
return ['count' => 0, 'contacts' => []];
|
||||
|
|
|
@ -76,7 +76,7 @@ class UpdateServerDirectory
|
|||
}
|
||||
|
||||
$accounts = json_decode($result, true);
|
||||
if (empty($accounts)) {
|
||||
if (!is_array($accounts)) {
|
||||
Logger::info('No contacts', ['url' => $gserver['url']]);
|
||||
return;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue