Merge branch 'stable' into develop
This commit is contained in:
commit
2b95a7e7cd
323 changed files with 21146 additions and 16271 deletions
|
|
@ -34,6 +34,7 @@ use Friendica\Core\L10n;
|
|||
use Friendica\Core\System;
|
||||
use Friendica\Core\Theme;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Profile;
|
||||
use Friendica\Module\Special\HTTPException as ModuleHTTPException;
|
||||
use Friendica\Network\HTTPException;
|
||||
|
|
@ -464,6 +465,11 @@ class App
|
|||
if (Core\Session::get('visitor_home') != $_GET["zrl"]) {
|
||||
Core\Session::set('my_url', $_GET['zrl']);
|
||||
Core\Session::set('authenticated', 0);
|
||||
|
||||
$remote_contact = Contact::getByURL($_GET['zrl'], false, ['subscribe']);
|
||||
if (!empty($remote_contact['subscribe'])) {
|
||||
$_SESSION['remote_comment'] = $remote_contact['subscribe'];
|
||||
}
|
||||
}
|
||||
|
||||
Model\Profile::zrlInit($this);
|
||||
|
|
|
|||
|
|
@ -232,7 +232,7 @@ class BaseURL
|
|||
{
|
||||
$parsed = @parse_url($url);
|
||||
|
||||
if (empty($parsed)) {
|
||||
if (empty($parsed) || empty($parsed['host'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -63,7 +63,6 @@ class Module
|
|||
'outbox',
|
||||
'poco',
|
||||
'post',
|
||||
'proxy',
|
||||
'pubsub',
|
||||
'pubsubhubbub',
|
||||
'receive',
|
||||
|
|
@ -265,6 +264,38 @@ class Module
|
|||
$logger->debug('index.php: page not found.', ['request_uri' => $server['REQUEST_URI'], 'address' => $server['REMOTE_ADDR'], 'query' => $server['QUERY_STRING']]);
|
||||
}
|
||||
|
||||
// @see https://github.com/tootsuite/mastodon/blob/c3aef491d66aec743a3a53e934a494f653745b61/config/initializers/cors.rb
|
||||
if (substr($_REQUEST['pagename'] ?? '', 0, 12) == '.well-known/') {
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Headers: *');
|
||||
header('Access-Control-Allow-Methods: ' . Router::GET);
|
||||
header('Access-Control-Allow-Credentials: false');
|
||||
} elseif (substr($_REQUEST['pagename'] ?? '', 0, 8) == 'profile/') {
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Headers: *');
|
||||
header('Access-Control-Allow-Methods: ' . Router::GET);
|
||||
header('Access-Control-Allow-Credentials: false');
|
||||
} elseif (substr($_REQUEST['pagename'] ?? '', 0, 4) == 'api/') {
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Headers: *');
|
||||
header('Access-Control-Allow-Methods: ' . implode(',', Router::ALLOWED_METHODS));
|
||||
header('Access-Control-Allow-Credentials: false');
|
||||
header('Access-Control-Expose-Headers: Link');
|
||||
} elseif (substr($_REQUEST['pagename'] ?? '', 0, 11) == 'oauth/token') {
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Headers: *');
|
||||
header('Access-Control-Allow-Methods: ' . Router::POST);
|
||||
header('Access-Control-Allow-Credentials: false');
|
||||
}
|
||||
|
||||
// @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS
|
||||
// @todo Check allowed methods per requested path
|
||||
if ($server['REQUEST_METHOD'] === Router::OPTIONS) {
|
||||
header('HTTP/1.1 204 No Content');
|
||||
header('Allow: ' . implode(',', Router::ALLOWED_METHODS));
|
||||
exit();
|
||||
}
|
||||
|
||||
$placeholder = '';
|
||||
|
||||
$profiler->set(microtime(true), 'ready');
|
||||
|
|
|
|||
|
|
@ -44,11 +44,12 @@ use Friendica\Network\HTTPException;
|
|||
*/
|
||||
class Router
|
||||
{
|
||||
const DELETE = 'DELETE';
|
||||
const GET = 'GET';
|
||||
const PATCH = 'PATCH';
|
||||
const POST = 'POST';
|
||||
const PUT = 'PUT';
|
||||
const DELETE = 'DELETE';
|
||||
const GET = 'GET';
|
||||
const PATCH = 'PATCH';
|
||||
const POST = 'POST';
|
||||
const PUT = 'PUT';
|
||||
const OPTIONS = 'OPTIONS';
|
||||
|
||||
const ALLOWED_METHODS = [
|
||||
self::DELETE,
|
||||
|
|
@ -56,6 +57,7 @@ class Router
|
|||
self::PATCH,
|
||||
self::POST,
|
||||
self::PUT,
|
||||
self::OPTIONS
|
||||
];
|
||||
|
||||
/** @var RouteCollector */
|
||||
|
|
|
|||
17
src/Collection/Api/Mastodon/Mentions.php
Normal file
17
src/Collection/Api/Mastodon/Mentions.php
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Collection\Api\Mastodon;
|
||||
|
||||
use Friendica\BaseCollection;
|
||||
use Friendica\Object\Api\Mastodon\Mention;
|
||||
|
||||
class Mentions extends BaseCollection
|
||||
{
|
||||
/**
|
||||
* @return Mention
|
||||
*/
|
||||
public function current(): Mention
|
||||
{
|
||||
return parent::current();
|
||||
}
|
||||
}
|
||||
|
|
@ -30,7 +30,6 @@ use Friendica\Core\Installer;
|
|||
use Friendica\Core\Theme;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Util\BasePath;
|
||||
use Friendica\Util\ConfigFileLoader;
|
||||
use RuntimeException;
|
||||
|
||||
class AutomaticInstallation extends Console
|
||||
|
|
@ -66,7 +65,7 @@ Options
|
|||
-H|--dbhost <host> The host of the mysql/mariadb database (env MYSQL_HOST)
|
||||
-p|--dbport <port> The port of the mysql/mariadb database (env MYSQL_PORT)
|
||||
-d|--dbdata <database> The name of the mysql/mariadb database (env MYSQL_DATABASE)
|
||||
-U|--dbuser <username> The username of the mysql/mariadb database login (env MYSQL_USER or MYSQL_USERNAME)
|
||||
-u|--dbuser <username> The username of the mysql/mariadb database login (env MYSQL_USER or MYSQL_USERNAME)
|
||||
-P|--dbpass <password> The password of the mysql/mariadb database login (env MYSQL_PASSWORD)
|
||||
-U|--url <url> The full base URL of Friendica - f.e. 'https://friendica.local/sub' (env FRIENDICA_URL)
|
||||
-B|--phppath <php_path> The path of the PHP binary (env FRIENDICA_PHP_PATH)
|
||||
|
|
@ -112,7 +111,7 @@ HELP;
|
|||
protected function doExecute()
|
||||
{
|
||||
// Initialise the app
|
||||
$this->out("Initializing setup...\n");
|
||||
$this->out("Initializing setup...");
|
||||
|
||||
$installer = new Installer();
|
||||
|
||||
|
|
@ -121,10 +120,10 @@ HELP;
|
|||
$basepath = new BasePath($basePathConf);
|
||||
$installer->setUpCache($configCache, $basepath->getPath());
|
||||
|
||||
$this->out(" Complete!\n\n");
|
||||
$this->out(" Complete!\n");
|
||||
|
||||
// Check Environment
|
||||
$this->out("Checking environment...\n");
|
||||
$this->out("Checking environment...");
|
||||
|
||||
$installer->resetChecks();
|
||||
|
||||
|
|
@ -133,24 +132,26 @@ HELP;
|
|||
throw new RuntimeException($errorMessage);
|
||||
}
|
||||
|
||||
$this->out(" Complete!\n\n");
|
||||
$this->out(" Complete!\n");
|
||||
|
||||
// if a config file is set,
|
||||
$config_file = $this->getOption(['f', 'file']);
|
||||
|
||||
if (!empty($config_file)) {
|
||||
|
||||
$this->out("Loading config file '$config_file'...");
|
||||
if (!file_exists($config_file)) {
|
||||
throw new RuntimeException("ERROR: Config file does not exist.\n");
|
||||
throw new RuntimeException("ERROR: Config file does not exist.");
|
||||
}
|
||||
|
||||
//reload the config cache
|
||||
$loader = new ConfigFileLoader($config_file);
|
||||
$loader->setupCache($configCache);
|
||||
|
||||
//append config file to the config cache
|
||||
$config = include($config_file);
|
||||
if (!is_array($config)) {
|
||||
throw new Exception('Error loading config file ' . $config_file);
|
||||
}
|
||||
$configCache->load($config, Cache::SOURCE_FILE);
|
||||
} else {
|
||||
// Creating config file
|
||||
$this->out("Creating config file...\n");
|
||||
$this->out("Creating config file...");
|
||||
|
||||
$save_db = $this->getOption(['s', 'savedb'], false);
|
||||
|
||||
|
|
@ -161,7 +162,7 @@ HELP;
|
|||
$this->getOption(['d', 'dbdata'],
|
||||
($save_db) ? getenv('MYSQL_DATABASE') : ''));
|
||||
$configCache->set('database', 'username',
|
||||
$this->getOption(['U', 'dbuser'],
|
||||
$this->getOption(['u', 'dbuser'],
|
||||
($save_db) ? getenv('MYSQL_USER') . getenv('MYSQL_USERNAME') : ''));
|
||||
$configCache->set('database', 'password',
|
||||
$this->getOption(['P', 'dbpass'],
|
||||
|
|
@ -202,10 +203,10 @@ HELP;
|
|||
$installer->createConfig($configCache);
|
||||
}
|
||||
|
||||
$this->out("Complete!\n\n");
|
||||
$this->out(" Complete!\n");
|
||||
|
||||
// Check database connection
|
||||
$this->out("Checking database...\n");
|
||||
$this->out("Checking database...");
|
||||
|
||||
$installer->resetChecks();
|
||||
|
||||
|
|
@ -214,7 +215,7 @@ HELP;
|
|||
throw new RuntimeException($errorMessage);
|
||||
}
|
||||
|
||||
$this->out(" Complete!\n\n");
|
||||
$this->out(" Complete!\n");
|
||||
|
||||
// Install database
|
||||
$this->out("Inserting data into database...\n");
|
||||
|
|
@ -228,24 +229,24 @@ HELP;
|
|||
|
||||
if (!empty($config_file) && $config_file != 'config' . DIRECTORY_SEPARATOR . 'local.config.php') {
|
||||
// Copy config file
|
||||
$this->out("Copying config file...\n");
|
||||
if (!copy($basePathConf . DIRECTORY_SEPARATOR . $config_file, $basePathConf . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'local.config.php')) {
|
||||
$this->out("Copying config file...");
|
||||
if (!copy($config_file, $basePathConf . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'local.config.php')) {
|
||||
throw new RuntimeException("ERROR: Saving config file failed. Please copy '$config_file' to '" . $basePathConf . "'" . DIRECTORY_SEPARATOR . "config" . DIRECTORY_SEPARATOR . "local.config.php' manually.\n");
|
||||
}
|
||||
}
|
||||
|
||||
$this->out(" Complete!\n\n");
|
||||
$this->out(" Complete!\n");
|
||||
|
||||
// Install theme
|
||||
$this->out("Installing theme\n");
|
||||
$this->out("Installing theme");
|
||||
if (!empty($this->config->get('system', 'theme'))) {
|
||||
Theme::install($this->config->get('system', 'theme'));
|
||||
$this->out(" Complete\n\n");
|
||||
$this->out(" Complete\n");
|
||||
} else {
|
||||
$this->out(" Theme setting is empty. Please check the file 'config/local.config.php'\n\n");
|
||||
$this->out(" Theme setting is empty. Please check the file 'config/local.config.php'\n");
|
||||
}
|
||||
|
||||
$this->out("\nInstallation is finished\n");
|
||||
$this->out("\nInstallation is finished");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ HELP;
|
|||
$output = ob_get_clean();
|
||||
break;
|
||||
case "dumpsql":
|
||||
DBStructure::writeStructure();
|
||||
ob_start();
|
||||
DBStructure::printStructure($basePath);
|
||||
$output = ob_get_clean();
|
||||
|
|
|
|||
|
|
@ -127,16 +127,6 @@ class Item
|
|||
$tag_type = substr($tag, 0, 1);
|
||||
//is it already replaced?
|
||||
if (strpos($tag, '[url=')) {
|
||||
// Checking for the alias that is used for OStatus
|
||||
$pattern = '/[@!]\[url\=(.*?)\](.*?)\[\/url\]/ism';
|
||||
if (preg_match($pattern, $tag, $matches)) {
|
||||
$data = Contact::getByURL($matches[1], false, ['alias', 'nick']);
|
||||
|
||||
if ($data['alias'] != '') {
|
||||
$newtag = '@[url=' . $data['alias'] . ']' . $data['nick'] . '[/url]';
|
||||
}
|
||||
}
|
||||
|
||||
return $replaced;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -953,6 +953,10 @@ class BBCode
|
|||
*/
|
||||
public static function fetchShareAttributes($text)
|
||||
{
|
||||
// See Issue https://github.com/friendica/friendica/issues/10454
|
||||
// Hashtags in usernames are expanded to links. This here is a quick fix.
|
||||
$text = preg_replace('/([@!#])\[url\=.*?\](.*?)\[\/url\]/ism', '$1$2', $text);
|
||||
|
||||
$attributes = [];
|
||||
if (!preg_match("/(.*?)\[share(.*?)\](.*)\[\/share\]/ism", $text, $matches)) {
|
||||
return $attributes;
|
||||
|
|
@ -997,7 +1001,7 @@ class BBCode
|
|||
$attributes[$field] = html_entity_decode($matches[2] ?? '', ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
$author_contact = Contact::getByURL($attributes['profile'], false, ['url', 'addr', 'name', 'micro']);
|
||||
$author_contact = Contact::getByURL($attributes['profile'], false, ['id', 'url', 'addr', 'name', 'micro']);
|
||||
$author_contact['url'] = ($author_contact['url'] ?? $attributes['profile']);
|
||||
$author_contact['addr'] = ($author_contact['addr'] ?? '') ?: Protocol::getAddrFromProfileUrl($attributes['profile']);
|
||||
|
||||
|
|
@ -1005,7 +1009,9 @@ class BBCode
|
|||
$attributes['avatar'] = ($author_contact['micro'] ?? '') ?: $attributes['avatar'];
|
||||
$attributes['profile'] = ($author_contact['url'] ?? '') ?: $attributes['profile'];
|
||||
|
||||
if ($attributes['avatar']) {
|
||||
if (!empty($author_contact['id'])) {
|
||||
$attributes['avatar'] = Contact::getAvatarUrlForId($author_contact['id'], ProxyUtils::SIZE_THUMB);
|
||||
} elseif ($attributes['avatar']) {
|
||||
$attributes['avatar'] = ProxyUtils::proxifyUrl($attributes['avatar'], false, ProxyUtils::SIZE_THUMB);
|
||||
}
|
||||
|
||||
|
|
@ -1039,7 +1045,9 @@ class BBCode
|
|||
|
||||
switch ($simplehtml) {
|
||||
case self::API:
|
||||
$text = ($is_quote_share? '<br>' : '') . '<p>' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ': </p>' . "\n" . $content;
|
||||
$text = ($is_quote_share? '<br>' : '') .
|
||||
'<p><b><a href="' . $attributes['link'] . '">' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . "</a>:</b> </p>\n" .
|
||||
'<blockquote class="shared_content">' . $content . '</blockquote>';
|
||||
break;
|
||||
case self::DIASPORA:
|
||||
if (stripos(Strings::normaliseLink($attributes['link']), 'http://twitter.com/') === 0) {
|
||||
|
|
@ -2194,9 +2202,7 @@ class BBCode
|
|||
}
|
||||
}
|
||||
|
||||
$success = Item::replaceTag($body, $inform, $profile_uid, $tag, $network);
|
||||
|
||||
if ($success['replaced']) {
|
||||
if (($success = Item::replaceTag($body, $inform, $profile_uid, $tag, $network)) && $success['replaced']) {
|
||||
$tagged[] = $tag;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@
|
|||
|
||||
namespace Friendica\Content\Widget;
|
||||
|
||||
use Friendica\Content\Feature;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\User;
|
||||
|
||||
/**
|
||||
* TagCloud widget
|
||||
|
|
@ -34,36 +34,27 @@ class CalendarExport
|
|||
{
|
||||
/**
|
||||
* Get the events widget.
|
||||
* @param int $uid
|
||||
*
|
||||
* @return string Formated HTML of the calendar widget.
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function getHTML() {
|
||||
$a = DI::app();
|
||||
|
||||
if (empty($a->data['user'])) {
|
||||
return;
|
||||
public static function getHTML(int $uid = 0) {
|
||||
if (empty($uid)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$owner_uid = intval($a->data['user']['uid']);
|
||||
|
||||
// The permission testing is a little bit tricky because we have to respect many cases.
|
||||
|
||||
// It's not the private events page (we don't get the $owner_uid for /events).
|
||||
if (!local_user() && !$owner_uid) {
|
||||
return;
|
||||
$user = User::getById($uid, ['nickname']);
|
||||
if (empty($user['nickname'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// $a->data is only available if the profile page is visited. If the visited page is not part
|
||||
// of the profile page it should be the personal /events page. So we can use $a->user.
|
||||
$user = ($a->data['user']['nickname'] ?? '') ?: $a->user['nickname'];
|
||||
|
||||
$tpl = Renderer::getMarkupTemplate("widget/events.tpl");
|
||||
$return = Renderer::replaceMacros($tpl, [
|
||||
'$etitle' => DI::l10n()->t("Export"),
|
||||
'$export_ical' => DI::l10n()->t("Export calendar as ical"),
|
||||
'$export_csv' => DI::l10n()->t("Export calendar as csv"),
|
||||
'$user' => $user
|
||||
'$user' => $user['nickname']
|
||||
]);
|
||||
|
||||
return $return;
|
||||
|
|
|
|||
|
|
@ -127,8 +127,8 @@ class TagCloud
|
|||
private static function tagCalc(array $arr)
|
||||
{
|
||||
$tags = [];
|
||||
$min = 1e9;
|
||||
$max = -1e9;
|
||||
$min = 1000000000.0;
|
||||
$max = -1000000000.0;
|
||||
$x = 0;
|
||||
|
||||
if (!$arr) {
|
||||
|
|
@ -145,7 +145,7 @@ class TagCloud
|
|||
}
|
||||
|
||||
usort($tags, 'self::tagsSort');
|
||||
$range = max(.01, $max - $min) * 1.0001;
|
||||
$range = max(0.01, $max - $min) * 1.0001;
|
||||
|
||||
for ($x = 0; $x < count($tags); $x ++) {
|
||||
$tags[$x][2] = 1 + floor(9 * ($tags[$x][1] - $min) / $range);
|
||||
|
|
|
|||
|
|
@ -172,6 +172,8 @@ HELP;
|
|||
|
||||
Friendica\DI::init($this->dice);
|
||||
|
||||
Renderer::registerTemplateEngine('Friendica\Render\FriendicaSmartyEngine');
|
||||
|
||||
/** @var Console $subconsole */
|
||||
$subconsole = $this->dice->create($className, [$subargs]);
|
||||
|
||||
|
|
|
|||
|
|
@ -465,7 +465,7 @@ class Installer
|
|||
|
||||
$status = $this->checkFunction('proc_open',
|
||||
DI::l10n()->t('Program execution functions'),
|
||||
DI::l10n()->t('Error: Program execution functions required but not enabled.'),
|
||||
DI::l10n()->t('Error: Program execution functions (proc_open) required but not enabled.'),
|
||||
true
|
||||
);
|
||||
$returnVal = $returnVal ? $status : false;
|
||||
|
|
|
|||
|
|
@ -116,16 +116,17 @@ class Session
|
|||
$session = DI::session();
|
||||
|
||||
$session->set('remote', []);
|
||||
$remote = [];
|
||||
|
||||
$remote_contacts = DBA::select('contact', ['id', 'uid'], ['nurl' => Strings::normaliseLink($session->get('my_url')), 'rel' => [Contact::FOLLOWER, Contact::FRIEND], 'self' => false]);
|
||||
while ($contact = DBA::fetch($remote_contacts)) {
|
||||
if (($contact['uid'] == 0) || Contact\User::isBlocked($contact['id'], $contact['uid'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$session->set('remote', [$contact['uid'] => $contact['id']]);
|
||||
$remote[$contact['uid']] = $contact['id'];
|
||||
}
|
||||
DBA::close($remote_contacts);
|
||||
$session->set('remote', $remote);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ use Exception;
|
|||
use Friendica\Core\Config\IConfig;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Model\Storage;
|
||||
use Friendica\Network\IHTTPRequest;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
|
||||
|
|
@ -60,6 +61,8 @@ class StorageManager
|
|||
private $logger;
|
||||
/** @var L10n */
|
||||
private $l10n;
|
||||
/** @var IHTTPRequest */
|
||||
private $httpRequest;
|
||||
|
||||
/** @var Storage\IStorage */
|
||||
private $currentBackend;
|
||||
|
|
@ -70,12 +73,13 @@ class StorageManager
|
|||
* @param LoggerInterface $logger
|
||||
* @param L10n $l10n
|
||||
*/
|
||||
public function __construct(Database $dba, IConfig $config, LoggerInterface $logger, L10n $l10n)
|
||||
public function __construct(Database $dba, IConfig $config, LoggerInterface $logger, L10n $l10n, IHTTPRequest $httpRequest)
|
||||
{
|
||||
$this->dba = $dba;
|
||||
$this->config = $config;
|
||||
$this->logger = $logger;
|
||||
$this->l10n = $l10n;
|
||||
$this->dba = $dba;
|
||||
$this->config = $config;
|
||||
$this->logger = $logger;
|
||||
$this->l10n = $l10n;
|
||||
$this->httpRequest = $httpRequest;
|
||||
$this->backends = $config->get('storage', 'backends', self::DEFAULT_BACKENDS);
|
||||
|
||||
$currentName = $this->config->get('storage', 'name', '');
|
||||
|
|
@ -126,6 +130,9 @@ class StorageManager
|
|||
case Storage\SystemResource::getName():
|
||||
$this->backendInstances[$name] = new Storage\SystemResource();
|
||||
break;
|
||||
case Storage\ExternalResource::getName():
|
||||
$this->backendInstances[$name] = new Storage\ExternalResource($this->httpRequest);
|
||||
break;
|
||||
default:
|
||||
$data = [
|
||||
'name' => $name,
|
||||
|
|
@ -158,7 +165,7 @@ class StorageManager
|
|||
public function isValidBackend(string $name = null, bool $onlyUserBackend = false)
|
||||
{
|
||||
return array_key_exists($name, $this->backends) ||
|
||||
(!$onlyUserBackend && $name === Storage\SystemResource::getName());
|
||||
(!$onlyUserBackend && in_array($name, [Storage\SystemResource::getName(), Storage\ExternalResource::getName()]));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -213,6 +213,8 @@ class Update
|
|||
if ($sendMail) {
|
||||
self::updateSuccessful($stored, $current);
|
||||
}
|
||||
} else {
|
||||
Logger::warning('Update lock could not be acquired');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
24
src/DI.php
24
src/DI.php
|
|
@ -287,14 +287,6 @@ abstract class DI
|
|||
return self::$dice->create(Factory\Api\Mastodon\Error::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Factory\Api\Mastodon\Field
|
||||
*/
|
||||
public static function mstdnField()
|
||||
{
|
||||
return self::$dice->create(Factory\Api\Mastodon\Field::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Factory\Api\Mastodon\FollowRequest
|
||||
*/
|
||||
|
|
@ -327,14 +319,6 @@ abstract class DI
|
|||
return self::$dice->create(Factory\Api\Mastodon\ListEntity::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Factory\Api\Mastodon\Mention
|
||||
*/
|
||||
public static function mstdnMention()
|
||||
{
|
||||
return self::$dice->create(Factory\Api\Mastodon\Mention::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Factory\Api\Mastodon\Notification
|
||||
*/
|
||||
|
|
@ -343,14 +327,6 @@ abstract class DI
|
|||
return self::$dice->create(Factory\Api\Mastodon\Notification::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Factory\Api\Mastodon\Tag
|
||||
*/
|
||||
public static function mstdnTag()
|
||||
{
|
||||
return self::$dice->create(Factory\Api\Mastodon\Tag::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Factory\Api\Twitter\User
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ namespace Friendica\Database;
|
|||
use Exception;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\User;
|
||||
|
|
@ -159,6 +160,108 @@ class DBStructure
|
|||
return DI::l10n()->t('Errors encountered performing database changes: ') . $message . EOL;
|
||||
}
|
||||
|
||||
public static function writeStructure()
|
||||
{
|
||||
$tables = [];
|
||||
foreach (self::definition(null) as $name => $definition) {
|
||||
$indexes = [[
|
||||
'name' => 'Name',
|
||||
'fields' => 'Fields',
|
||||
],
|
||||
[
|
||||
'name' => '-',
|
||||
'fields' => '-',
|
||||
]];
|
||||
|
||||
$lengths = ['name' => 4, 'fields' => 6];
|
||||
foreach ($definition['indexes'] as $key => $value) {
|
||||
$fieldlist = implode(', ', $value);
|
||||
$indexes[] = ['name' => $key, 'fields' => $fieldlist];
|
||||
$lengths['name'] = max($lengths['name'], strlen($key));
|
||||
$lengths['fields'] = max($lengths['fields'], strlen($fieldlist));
|
||||
}
|
||||
|
||||
array_walk_recursive($indexes, function(&$value, $key) use ($lengths)
|
||||
{
|
||||
$value = str_pad($value, $lengths[$key], $value === '-' ? '-' : ' ');
|
||||
});
|
||||
|
||||
$foreign = [];
|
||||
$fields = [[
|
||||
'name' => 'Field',
|
||||
'comment' => 'Description',
|
||||
'type' => 'Type',
|
||||
'null' => 'Null',
|
||||
'primary' => 'Key',
|
||||
'default' => 'Default',
|
||||
'extra' => 'Extra',
|
||||
],
|
||||
[
|
||||
'name' => '-',
|
||||
'comment' => '-',
|
||||
'type' => '-',
|
||||
'null' => '-',
|
||||
'primary' => '-',
|
||||
'default' => '-',
|
||||
'extra' => '-',
|
||||
]];
|
||||
$lengths = [
|
||||
'name' => 5,
|
||||
'comment' => 11,
|
||||
'type' => 4,
|
||||
'null' => 4,
|
||||
'primary' => 3,
|
||||
'default' => 7,
|
||||
'extra' => 5,
|
||||
];
|
||||
foreach ($definition['fields'] as $key => $value) {
|
||||
$field = [];
|
||||
$field['name'] = $key;
|
||||
$field['comment'] = $value['comment'] ?? '';
|
||||
$field['type'] = $value['type'];
|
||||
$field['null'] = ($value['not null'] ?? false) ? 'NO' : 'YES';
|
||||
$field['primary'] = ($value['primary'] ?? false) ? 'PRI' : '';
|
||||
$field['default'] = $value['default'] ?? 'NULL';
|
||||
$field['extra'] = $value['extra'] ?? '';
|
||||
|
||||
foreach ($field as $fieldname => $fieldvalue) {
|
||||
$lengths[$fieldname] = max($lengths[$fieldname] ?? 0, strlen($fieldvalue));
|
||||
}
|
||||
$fields[] = $field;
|
||||
|
||||
if (!empty($value['foreign'])) {
|
||||
$foreign[] = [
|
||||
'field' => $key,
|
||||
'targettable' => array_keys($value['foreign'])[0],
|
||||
'targetfield' => array_values($value['foreign'])[0]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
array_walk_recursive($fields, function(&$value, $key) use ($lengths)
|
||||
{
|
||||
$value = str_pad($value, $lengths[$key], $value === '-' ? '-' : ' ');
|
||||
});
|
||||
|
||||
$tables[] = ['name' => $name, 'comment' => $definition['comment']];
|
||||
$content = Renderer::replaceMacros(Renderer::getMarkupTemplate('structure.tpl'), [
|
||||
'$name' => $name,
|
||||
'$comment' => $definition['comment'],
|
||||
'$fields' => $fields,
|
||||
'$indexes' => $indexes,
|
||||
'$foreign' => $foreign,
|
||||
]);
|
||||
$filename = DI::basePath() . '/doc/database/db_' . $name . '.md';
|
||||
file_put_contents($filename, $content);
|
||||
}
|
||||
asort($tables);
|
||||
$content = Renderer::replaceMacros(Renderer::getMarkupTemplate('tables.tpl'), [
|
||||
'$tables' => $tables,
|
||||
]);
|
||||
$filename = DI::basePath() . '/doc/database.md';
|
||||
file_put_contents($filename, $content);
|
||||
}
|
||||
|
||||
public static function printStructure($basePath)
|
||||
{
|
||||
$database = self::definition($basePath, false);
|
||||
|
|
@ -241,6 +344,12 @@ class DBStructure
|
|||
// Assign all field that are present in the table
|
||||
foreach ($fieldnames as $field) {
|
||||
if (isset($data[$field])) {
|
||||
// Limit the length of varchar, varbinary, char and binrary fields
|
||||
if (is_string($data[$field]) && preg_match("/char\((\d*)\)/", $definition[$table]['fields'][$field]['type'], $result)) {
|
||||
$data[$field] = mb_substr($data[$field], 0, $result[1]);
|
||||
} elseif (is_string($data[$field]) && preg_match("/binary\((\d*)\)/", $definition[$table]['fields'][$field]['type'], $result)) {
|
||||
$data[$field] = substr($data[$field], 0, $result[1]);
|
||||
}
|
||||
$fields[$field] = $data[$field];
|
||||
}
|
||||
}
|
||||
|
|
@ -1200,7 +1309,7 @@ class DBStructure
|
|||
if ($verbose) {
|
||||
echo "Zero contact added\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif (self::existsTable('contact') && $verbose) {
|
||||
echo "Zero contact already added\n";
|
||||
} elseif ($verbose) {
|
||||
|
|
@ -1224,7 +1333,7 @@ class DBStructure
|
|||
|
||||
if (self::existsTable('permissionset')) {
|
||||
if (!DBA::exists('permissionset', ['id' => 0])) {
|
||||
DBA::insert('permissionset', ['allow_cid' => '', 'allow_gid' => '', 'deny_cid' => '', 'deny_gid' => '']);
|
||||
DBA::insert('permissionset', ['allow_cid' => '', 'allow_gid' => '', 'deny_cid' => '', 'deny_gid' => '']);
|
||||
$lastid = DBA::lastInsertId();
|
||||
if ($lastid != 0) {
|
||||
DBA::update('permissionset', ['id' => 0], ['id' => $lastid]);
|
||||
|
|
@ -1259,7 +1368,7 @@ class DBStructure
|
|||
} elseif ($verbose) {
|
||||
echo "permissionset: Table not found\n";
|
||||
}
|
||||
|
||||
|
||||
if (!self::existsForeignKeyForField('tokens', 'client_id')) {
|
||||
$tokens = DBA::p("SELECT `tokens`.`id` FROM `tokens`
|
||||
LEFT JOIN `clients` ON `clients`.`client_id` = `tokens`.`client_id`
|
||||
|
|
|
|||
|
|
@ -29,34 +29,36 @@ use Friendica\Model\Contact;
|
|||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Repository\PermissionSet;
|
||||
use Friendica\Repository\ProfileField;
|
||||
use ImagickException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class Account extends BaseFactory
|
||||
{
|
||||
/** @var BaseURL */
|
||||
protected $baseUrl;
|
||||
private $baseUrl;
|
||||
/** @var ProfileField */
|
||||
protected $profileField;
|
||||
private $profileFieldRepo;
|
||||
/** @var Field */
|
||||
protected $mstdnField;
|
||||
private $mstdnFieldFactory;
|
||||
|
||||
public function __construct(LoggerInterface $logger, BaseURL $baseURL, ProfileField $profileField, Field $mstdnField)
|
||||
public function __construct(LoggerInterface $logger, BaseURL $baseURL, ProfileField $profileFieldRepo, Field $mstdnFieldFactory)
|
||||
{
|
||||
parent::__construct($logger);
|
||||
|
||||
$this->baseUrl = $baseURL;
|
||||
$this->profileField = $profileField;
|
||||
$this->mstdnField = $mstdnField;
|
||||
$this->baseUrl = $baseURL;
|
||||
$this->profileFieldRepo = $profileFieldRepo;
|
||||
$this->mstdnFieldFactory = $mstdnFieldFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $contactId
|
||||
* @param int $uid Public contact (=0) or owner user id
|
||||
*
|
||||
* @return \Friendica\Object\Api\Mastodon\Account
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
* @throws ImagickException|HTTPException\NotFoundException
|
||||
*/
|
||||
public function createFromContactId(int $contactId, $uid = 0)
|
||||
public function createFromContactId(int $contactId, $uid = 0): \Friendica\Object\Api\Mastodon\Account
|
||||
{
|
||||
$cdata = Contact::getPublicAndUserContacID($contactId, $uid);
|
||||
if (!empty($cdata)) {
|
||||
|
|
@ -64,7 +66,7 @@ class Account extends BaseFactory
|
|||
$userContact = Contact::getById($cdata['user']);
|
||||
} else {
|
||||
$publicContact = Contact::getById($contactId);
|
||||
$userContact = [];
|
||||
$userContact = [];
|
||||
}
|
||||
|
||||
if (empty($publicContact)) {
|
||||
|
|
@ -75,8 +77,8 @@ class Account extends BaseFactory
|
|||
|
||||
$self_contact = Contact::selectFirst(['uid'], ['nurl' => $publicContact['nurl'], 'self' => true]);
|
||||
if (!empty($self_contact['uid'])) {
|
||||
$profileFields = $this->profileField->select(['uid' => $self_contact['uid'], 'psid' => PermissionSet::PUBLIC]);
|
||||
$fields = $this->mstdnField->createFromProfileFields($profileFields);
|
||||
$profileFields = $this->profileFieldRepo->select(['uid' => $self_contact['uid'], 'psid' => PermissionSet::PUBLIC]);
|
||||
$fields = $this->mstdnFieldFactory->createFromProfileFields($profileFields);
|
||||
} else {
|
||||
$fields = new Fields();
|
||||
}
|
||||
|
|
@ -87,18 +89,17 @@ class Account extends BaseFactory
|
|||
/**
|
||||
* @param int $userId
|
||||
* @return \Friendica\Object\Api\Mastodon\Account
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
* @throws ImagickException|HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public function createFromUserId(int $userId)
|
||||
public function createFromUserId(int $userId): \Friendica\Object\Api\Mastodon\Account
|
||||
{
|
||||
$publicContact = Contact::selectFirst([], ['uid' => $userId, 'self' => true]);
|
||||
|
||||
$profileFields = $this->profileField->select(['uid' => $userId, 'psid' => PermissionSet::PUBLIC]);
|
||||
$fields = $this->mstdnField->createFromProfileFields($profileFields);
|
||||
$profileFields = $this->profileFieldRepo->select(['uid' => $userId, 'psid' => PermissionSet::PUBLIC]);
|
||||
$fields = $this->mstdnFieldFactory->createFromProfileFields($profileFields);
|
||||
|
||||
$apcontact = APContact::getByURL($publicContact['url'], false);
|
||||
$apContact = APContact::getByURL($publicContact['url'], false);
|
||||
|
||||
return new \Friendica\Object\Api\Mastodon\Account($this->baseUrl, $publicContact, $fields, $apcontact);
|
||||
return new \Friendica\Object\Api\Mastodon\Account($this->baseUrl, $publicContact, $fields, $apContact);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,28 +22,41 @@
|
|||
namespace Friendica\Factory\Api\Mastodon;
|
||||
|
||||
use Friendica\BaseFactory;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Network\HTTPException\InternalServerErrorException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class Application extends BaseFactory
|
||||
{
|
||||
/** @var Database */
|
||||
private $dba;
|
||||
|
||||
public function __construct(LoggerInterface $logger, Database $dba)
|
||||
{
|
||||
parent::__construct($logger);
|
||||
$this->dba = $dba;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id Application ID
|
||||
*
|
||||
* @return \Friendica\Object\Api\Mastodon\Application
|
||||
*
|
||||
* @throws InternalServerErrorException
|
||||
*/
|
||||
public function createFromApplicationId(int $id)
|
||||
public function createFromApplicationId(int $id): \Friendica\Object\Api\Mastodon\Application
|
||||
{
|
||||
$application = DBA::selectFirst('application', ['client_id', 'client_secret', 'id', 'name', 'redirect_uri', 'website'], ['id' => $id]);
|
||||
if (!DBA::isResult($application)) {
|
||||
return [];
|
||||
$application = $this->dba->selectFirst('application', ['client_id', 'client_secret', 'id', 'name', 'redirect_uri', 'website'], ['id' => $id]);
|
||||
if (!$this->dba->isResult($application)) {
|
||||
throw new InternalServerErrorException(sprintf("ID '%s' not found", $id));
|
||||
}
|
||||
|
||||
$object = new \Friendica\Object\Api\Mastodon\Application(
|
||||
return new \Friendica\Object\Api\Mastodon\Application(
|
||||
$application['name'],
|
||||
$application['client_id'],
|
||||
$application['client_secret'],
|
||||
$application['id'],
|
||||
$application['redirect_uri'],
|
||||
$application['website']);
|
||||
|
||||
return $object->toArray();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,11 +23,9 @@ namespace Friendica\Factory\Api\Mastodon;
|
|||
|
||||
use Friendica\App\BaseURL;
|
||||
use Friendica\BaseFactory;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Photo;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Repository\ProfileField;
|
||||
use Friendica\Util\Images;
|
||||
use Friendica\Util\Proxy;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
|
@ -35,32 +33,24 @@ use Psr\Log\LoggerInterface;
|
|||
class Attachment extends BaseFactory
|
||||
{
|
||||
/** @var BaseURL */
|
||||
protected $baseUrl;
|
||||
/** @var ProfileField */
|
||||
protected $profileField;
|
||||
/** @var Field */
|
||||
protected $mstdnField;
|
||||
private $baseUrl;
|
||||
|
||||
public function __construct(LoggerInterface $logger, BaseURL $baseURL, ProfileField $profileField, Field $mstdnField)
|
||||
public function __construct(LoggerInterface $logger, BaseURL $baseURL)
|
||||
{
|
||||
parent::__construct($logger);
|
||||
|
||||
$this->baseUrl = $baseURL;
|
||||
$this->profileField = $profileField;
|
||||
$this->mstdnField = $mstdnField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $uriId Uri-ID of the attachments
|
||||
* @return array
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public function createFromUriId(int $uriId)
|
||||
public function createFromUriId(int $uriId): array
|
||||
{
|
||||
$attachments = [];
|
||||
foreach (Post\Media::getByURIId($uriId, [Post\Media::AUDIO, Post\Media::VIDEO, Post\Media::IMAGE]) as $attachment) {
|
||||
|
||||
$filetype = !empty($attachment['mimetype']) ? strtolower(substr($attachment['mimetype'], 0, strpos($attachment['mimetype'], '/'))) : '';
|
||||
|
||||
if (($filetype == 'audio') || ($attachment['type'] == Post\Media::AUDIO)) {
|
||||
|
|
@ -77,20 +67,19 @@ class Attachment extends BaseFactory
|
|||
|
||||
$remote = $attachment['url'];
|
||||
if ($type == 'image') {
|
||||
if (Proxy::isLocalImage($attachment['url'])) {
|
||||
$url = $attachment['url'];
|
||||
$preview = $attachment['preview'] ?? $url;
|
||||
$remote = '';
|
||||
} else {
|
||||
$url = Proxy::proxifyUrl($attachment['url']);
|
||||
$preview = Proxy::proxifyUrl($attachment['url'], false, Proxy::SIZE_SMALL);
|
||||
}
|
||||
$url = Post\Media::getPreviewUrlForId($attachment['id']);
|
||||
$preview = Post\Media::getPreviewUrlForId($attachment['id'], Proxy::SIZE_SMALL);
|
||||
} else {
|
||||
$url = '';
|
||||
$preview = '';
|
||||
$url = $attachment['url'];
|
||||
|
||||
if (!empty($attachment['preview'])) {
|
||||
$preview = Post\Media::getPreviewUrlForId($attachment['id'], Proxy::SIZE_SMALL);
|
||||
} else {
|
||||
$preview = '';
|
||||
}
|
||||
}
|
||||
|
||||
$object = new \Friendica\Object\Api\Mastodon\Attachment($attachment, $type, $url, $preview, $remote);
|
||||
$object = new \Friendica\Object\Api\Mastodon\Attachment($attachment, $type, $url, $preview, $remote);
|
||||
$attachments[] = $object->toArray();
|
||||
}
|
||||
|
||||
|
|
@ -99,27 +88,27 @@ class Attachment extends BaseFactory
|
|||
|
||||
/**
|
||||
* @param int $id id of the photo
|
||||
*
|
||||
* @return array
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public function createFromPhoto(int $id)
|
||||
public function createFromPhoto(int $id): array
|
||||
{
|
||||
$photo = Photo::selectFirst(['resource-id', 'uid', 'id', 'title', 'type'], ['id' => $id]);
|
||||
if (empty($photo)) {
|
||||
return null;
|
||||
return [];
|
||||
}
|
||||
|
||||
$attachment = ['id' => $photo['id'], 'description' => $photo['title']];
|
||||
|
||||
$phototypes = Images::supportedTypes();
|
||||
$ext = $phototypes[$photo['type']];
|
||||
$photoTypes = Images::supportedTypes();
|
||||
$ext = $photoTypes[$photo['type']];
|
||||
|
||||
$url = DI::baseUrl() . '/photo/' . $photo['resource-id'] . '-0.' . $ext;
|
||||
$url = $this->baseUrl . '/photo/' . $photo['resource-id'] . '-0.' . $ext;
|
||||
|
||||
$preview = Photo::selectFirst(['scale'], ["`resource-id` = ? AND `uid` = ? AND `scale` > ?", $photo['resource-id'], $photo['uid'], 0], ['order' => ['scale']]);
|
||||
if (empty($scale)) {
|
||||
$preview_url = DI::baseUrl() . '/photo/' . $photo['resource-id'] . '-' . $preview['scale'] . '.' . $ext;
|
||||
$preview_url = $this->baseUrl . '/photo/' . $photo['resource-id'] . '-' . $preview['scale'] . '.' . $ext;
|
||||
} else {
|
||||
$preview_url = '';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,11 +31,12 @@ class Card extends BaseFactory
|
|||
{
|
||||
/**
|
||||
* @param int $uriId Uri-ID of the item
|
||||
*
|
||||
* @return \Friendica\Object\Api\Mastodon\Card
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
* @throws \ImagickException*@throws \Exception
|
||||
*/
|
||||
public function createFromUriId(int $uriId)
|
||||
public function createFromUriId(int $uriId): \Friendica\Object\Api\Mastodon\Card
|
||||
{
|
||||
$item = Post::selectFirst(['body'], ['uri-id' => $uriId]);
|
||||
if (!empty($item['body'])) {
|
||||
|
|
|
|||
|
|
@ -22,13 +22,33 @@
|
|||
namespace Friendica\Factory\Api\Mastodon;
|
||||
|
||||
use Friendica\BaseFactory;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Network\HTTPException;
|
||||
use ImagickException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class Conversation extends BaseFactory
|
||||
{
|
||||
public function CreateFromConvId(int $id)
|
||||
/** @var Database */
|
||||
private $dba;
|
||||
/** @var Status */
|
||||
private $mstdnStatusFactory;
|
||||
/** @var Account */
|
||||
private $mstdnAccountFactory;
|
||||
|
||||
public function __construct(LoggerInterface $logger, Database $dba, Status $mstdnStatusFactory, Account $mstdnAccountFactoryFactory)
|
||||
{
|
||||
parent::__construct($logger);
|
||||
$this->dba = $dba;
|
||||
$this->mstdnStatusFactory = $mstdnStatusFactory;
|
||||
$this->mstdnAccountFactory = $mstdnAccountFactoryFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ImagickException|HTTPException\InternalServerErrorException|HTTPException\NotFoundException
|
||||
*/
|
||||
public function CreateFromConvId(int $id): \Friendica\Object\Api\Mastodon\Conversation
|
||||
{
|
||||
$accounts = [];
|
||||
$unread = false;
|
||||
|
|
@ -36,8 +56,8 @@ class Conversation extends BaseFactory
|
|||
|
||||
$ids = [];
|
||||
|
||||
$mails = DBA::select('mail', ['id', 'from-url', 'uid', 'seen'], ['convid' => $id], ['order' => ['id' => true]]);
|
||||
while ($mail = DBA::fetch($mails)) {
|
||||
$mails = $this->dba->select('mail', ['id', 'from-url', 'uid', 'seen'], ['convid' => $id], ['order' => ['id' => true]]);
|
||||
while ($mail = $this->dba->fetch($mails)) {
|
||||
if (!$mail['seen']) {
|
||||
$unread = true;
|
||||
}
|
||||
|
|
@ -50,10 +70,10 @@ class Conversation extends BaseFactory
|
|||
$ids[] = $id;
|
||||
|
||||
if (empty($last_status)) {
|
||||
$last_status = DI::mstdnStatus()->createFromMailId($mail['id']);
|
||||
$last_status = $this->mstdnStatusFactory->createFromMailId($mail['id']);
|
||||
}
|
||||
|
||||
$accounts[] = DI::mstdnAccount()->createFromContactId($id, 0);
|
||||
$accounts[] = $this->mstdnAccountFactory->createFromContactId($id, 0);
|
||||
}
|
||||
|
||||
return new \Friendica\Object\Api\Mastodon\Conversation($id, $accounts, $unread, $last_status);
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ use Friendica\Collection\Api\Mastodon\Emojis;
|
|||
|
||||
class Emoji extends BaseFactory
|
||||
{
|
||||
public function create(string $shortcode, string $url)
|
||||
public function create(string $shortcode, string $url): \Friendica\Object\Api\Mastodon\Emoji
|
||||
{
|
||||
return new \Friendica\Object\Api\Mastodon\Emoji($shortcode, $url);
|
||||
}
|
||||
|
|
@ -35,7 +35,7 @@ class Emoji extends BaseFactory
|
|||
* @param array $smilies
|
||||
* @return Emojis
|
||||
*/
|
||||
public function createCollectionFromSmilies(array $smilies)
|
||||
public function createCollectionFromSmilies(array $smilies): Emojis
|
||||
{
|
||||
$prototype = null;
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ class Emoji extends BaseFactory
|
|||
|
||||
if ($prototype === null) {
|
||||
$prototype = $this->create($shortcode, $url);
|
||||
$emojis[] = $prototype;
|
||||
$emojis[] = $prototype;
|
||||
} else {
|
||||
$emojis[] = \Friendica\Object\Api\Mastodon\Emoji::createFromPrototype($prototype, $shortcode, $url);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,65 +21,82 @@
|
|||
|
||||
namespace Friendica\Factory\Api\Mastodon;
|
||||
|
||||
use Friendica\App\Arguments;
|
||||
use Friendica\BaseFactory;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\DI;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/** @todo A Factory shouldn't return something to the frontpage, it's for creating content, not showing it */
|
||||
class Error extends BaseFactory
|
||||
{
|
||||
/** @var Arguments */
|
||||
private $args;
|
||||
/** @var string[] The $_SERVER array */
|
||||
private $server;
|
||||
/** @var L10n */
|
||||
private $l10n;
|
||||
|
||||
public function __construct(LoggerInterface $logger, Arguments $args, L10n $l10n, array $server)
|
||||
{
|
||||
parent::__construct($logger);
|
||||
$this->args = $args;
|
||||
$this->server = $server;
|
||||
$this->l10n = $l10n;
|
||||
}
|
||||
|
||||
private function logError(int $errorno, string $error)
|
||||
{
|
||||
Logger::info('API Error', ['no' => $errorno, 'error' => $error, 'method' => $_SERVER['REQUEST_METHOD'] ?? '', 'command' => DI::args()->getQueryString(), 'user-agent' => $_SERVER['HTTP_USER_AGENT'] ?? '']);
|
||||
$this->logger->info('API Error', ['no' => $errorno, 'error' => $error, 'method' => $this->server['REQUEST_METHOD'] ?? '', 'command' => $this->args->getQueryString(), 'user-agent' => $this->server['HTTP_USER_AGENT'] ?? '']);
|
||||
}
|
||||
|
||||
public function RecordNotFound()
|
||||
{
|
||||
$error = DI::l10n()->t('Record not found');
|
||||
$error = $this->l10n->t('Record not found');
|
||||
$error_description = '';
|
||||
$errorobj = New \Friendica\Object\Api\Mastodon\Error($error, $error_description);
|
||||
$errorObj = new \Friendica\Object\Api\Mastodon\Error($error, $error_description);
|
||||
|
||||
$this->logError(404, $error);
|
||||
System::jsonError(404, $errorobj->toArray());
|
||||
System::jsonError(404, $errorObj->toArray());
|
||||
}
|
||||
|
||||
public function UnprocessableEntity(string $error = '')
|
||||
{
|
||||
$error = $error ?: DI::l10n()->t('Unprocessable Entity');
|
||||
$error = $error ?: $this->l10n->t('Unprocessable Entity');
|
||||
$error_description = '';
|
||||
$errorobj = New \Friendica\Object\Api\Mastodon\Error($error, $error_description);
|
||||
$errorObj = new \Friendica\Object\Api\Mastodon\Error($error, $error_description);
|
||||
|
||||
$this->logError(422, $error);
|
||||
System::jsonError(422, $errorobj->toArray());
|
||||
System::jsonError(422, $errorObj->toArray());
|
||||
}
|
||||
|
||||
public function Unauthorized(string $error = '')
|
||||
{
|
||||
$error = $error ?: DI::l10n()->t('Unauthorized');
|
||||
$error = $error ?: $this->l10n->t('Unauthorized');
|
||||
$error_description = '';
|
||||
$errorobj = New \Friendica\Object\Api\Mastodon\Error($error, $error_description);
|
||||
$errorObj = new \Friendica\Object\Api\Mastodon\Error($error, $error_description);
|
||||
|
||||
$this->logError(401, $error);
|
||||
System::jsonError(401, $errorobj->toArray());
|
||||
System::jsonError(401, $errorObj->toArray());
|
||||
}
|
||||
|
||||
public function Forbidden(string $error = '')
|
||||
{
|
||||
$error = $error ?: DI::l10n()->t('Token is not authorized with a valid user or is missing a required scope');
|
||||
$error = $error ?: $this->l10n->t('Token is not authorized with a valid user or is missing a required scope');
|
||||
$error_description = '';
|
||||
$errorobj = New \Friendica\Object\Api\Mastodon\Error($error, $error_description);
|
||||
$errorObj = new \Friendica\Object\Api\Mastodon\Error($error, $error_description);
|
||||
|
||||
$this->logError(403, $error);
|
||||
System::jsonError(403, $errorobj->toArray());
|
||||
System::jsonError(403, $errorObj->toArray());
|
||||
}
|
||||
|
||||
public function InternalError(string $error = '')
|
||||
{
|
||||
$error = $error ?: DI::l10n()->t('Internal Server Error');
|
||||
$error = $error ?: $this->l10n->t('Internal Server Error');
|
||||
$error_description = '';
|
||||
$errorobj = New \Friendica\Object\Api\Mastodon\Error($error, $error_description);
|
||||
$errorObj = new \Friendica\Object\Api\Mastodon\Error($error, $error_description);
|
||||
|
||||
$this->logError(500, $error);
|
||||
System::jsonError(500, $errorobj->toArray());
|
||||
System::jsonError(500, $errorObj->toArray());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,10 +32,10 @@ class Field extends BaseFactory
|
|||
{
|
||||
/**
|
||||
* @param ProfileField $profileField
|
||||
* @return \Friendica\Api\Entity\Mastodon\Field
|
||||
* @return \Friendica\Object\Api\Mastodon\Field
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public function createFromProfileField(ProfileField $profileField)
|
||||
public function createFromProfileField(ProfileField $profileField): \Friendica\Object\Api\Mastodon\Field
|
||||
{
|
||||
return new \Friendica\Object\Api\Mastodon\Field($profileField->label, BBCode::convert($profileField->value, false, BBCode::ACTIVITYPUB));
|
||||
}
|
||||
|
|
@ -45,7 +45,7 @@ class Field extends BaseFactory
|
|||
* @return Fields
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public function createFromProfileFields(ProfileFields $profileFields)
|
||||
public function createFromProfileFields(ProfileFields $profileFields): Fields
|
||||
{
|
||||
$fields = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -27,12 +27,13 @@ use Friendica\Model\APContact;
|
|||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Introduction;
|
||||
use Friendica\Network\HTTPException;
|
||||
use ImagickException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class FollowRequest extends BaseFactory
|
||||
{
|
||||
/** @var BaseURL */
|
||||
protected $baseUrl;
|
||||
private $baseUrl;
|
||||
|
||||
public function __construct(LoggerInterface $logger, BaseURL $baseURL)
|
||||
{
|
||||
|
|
@ -44,10 +45,9 @@ class FollowRequest extends BaseFactory
|
|||
/**
|
||||
* @param Introduction $introduction
|
||||
* @return \Friendica\Object\Api\Mastodon\FollowRequest
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
* @throws ImagickException|HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public function createFromIntroduction(Introduction $introduction)
|
||||
public function createFromIntroduction(Introduction $introduction): \Friendica\Object\Api\Mastodon\FollowRequest
|
||||
{
|
||||
$cdata = Contact::getPublicAndUserContacID($introduction->{'contact-id'}, $introduction->uid);
|
||||
|
||||
|
|
@ -57,10 +57,10 @@ class FollowRequest extends BaseFactory
|
|||
}
|
||||
|
||||
$publicContact = Contact::getById($cdata['public']);
|
||||
$userContact = Contact::getById($cdata['user']);
|
||||
$userContact = Contact::getById($cdata['user']);
|
||||
|
||||
$apcontact = APContact::getByURL($publicContact['url'], false);
|
||||
$apContact = APContact::getByURL($publicContact['url'], false);
|
||||
|
||||
return new \Friendica\Object\Api\Mastodon\FollowRequest($this->baseUrl, $introduction->id, $publicContact, $apcontact, $userContact);
|
||||
return new \Friendica\Object\Api\Mastodon\FollowRequest($this->baseUrl, $introduction->id, $publicContact, $apContact, $userContact);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,13 +22,27 @@
|
|||
namespace Friendica\Factory\Api\Mastodon;
|
||||
|
||||
use Friendica\BaseFactory;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Network\HTTPException\InternalServerErrorException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class ListEntity extends BaseFactory
|
||||
{
|
||||
public function createFromGroupId(int $id)
|
||||
/** @var Database */
|
||||
private $dba;
|
||||
|
||||
public function __construct(LoggerInterface $logger, Database $dba)
|
||||
{
|
||||
$group = DBA::selectFirst('group', ['name'], ['id' => $id, 'deleted' => false]);
|
||||
parent::__construct($logger);
|
||||
$this->dba = $dba;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InternalServerErrorException
|
||||
*/
|
||||
public function createFromGroupId(int $id): \Friendica\Object\Api\Mastodon\ListEntity
|
||||
{
|
||||
$group = $this->dba->selectFirst('group', ['name'], ['id' => $id, 'deleted' => false]);
|
||||
return new \Friendica\Object\Api\Mastodon\ListEntity($id, $group['name'] ?? '', 'list');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,44 +23,36 @@ namespace Friendica\Factory\Api\Mastodon;
|
|||
|
||||
use Friendica\App\BaseURL;
|
||||
use Friendica\BaseFactory;
|
||||
use Friendica\Collection\Api\Mastodon\Mentions;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Repository\ProfileField;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class Mention extends BaseFactory
|
||||
{
|
||||
/** @var BaseURL */
|
||||
protected $baseUrl;
|
||||
/** @var ProfileField */
|
||||
protected $profileField;
|
||||
/** @var Field */
|
||||
protected $mstdnField;
|
||||
private $baseUrl;
|
||||
|
||||
public function __construct(LoggerInterface $logger, BaseURL $baseURL, ProfileField $profileField, Field $mstdnField)
|
||||
public function __construct(LoggerInterface $logger, BaseURL $baseURL)
|
||||
{
|
||||
parent::__construct($logger);
|
||||
|
||||
$this->baseUrl = $baseURL;
|
||||
$this->profileField = $profileField;
|
||||
$this->mstdnField = $mstdnField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $uriId Uri-ID of the item
|
||||
* @return array
|
||||
* @return Mentions
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public function createFromUriId(int $uriId)
|
||||
public function createFromUriId(int $uriId): Mentions
|
||||
{
|
||||
$mentions = [];
|
||||
$tags = Tag::getByURIId($uriId, [Tag::MENTION, Tag::EXCLUSIVE_MENTION, Tag::IMPLICIT_MENTION]);
|
||||
$mentions = new Mentions();
|
||||
$tags = Tag::getByURIId($uriId, [Tag::MENTION, Tag::EXCLUSIVE_MENTION, Tag::IMPLICIT_MENTION]);
|
||||
foreach ($tags as $tag) {
|
||||
$contact = Contact::getByURL($tag['url'], false);
|
||||
$mention = new \Friendica\Object\Api\Mastodon\Mention($this->baseUrl, $tag, $contact);
|
||||
$mentions[] = $mention->toArray();
|
||||
$contact = Contact::getByURL($tag['url'], false);
|
||||
$mentions[] = new \Friendica\Object\Api\Mastodon\Mention($this->baseUrl, $tag, $contact);
|
||||
}
|
||||
return $mentions;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,25 +22,36 @@
|
|||
namespace Friendica\Factory\Api\Mastodon;
|
||||
|
||||
use Friendica\BaseFactory;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Notification as ModelNotification;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Model\Verb;
|
||||
use Friendica\Protocol\Activity;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class Notification extends BaseFactory
|
||||
{
|
||||
public function createFromNotifyId(int $id)
|
||||
/** @var Database */
|
||||
private $dba;
|
||||
/** @var Account */
|
||||
private $mstdnAccountFactory;
|
||||
/** @var Status */
|
||||
private $mstdnStatusFactory;
|
||||
|
||||
public function __construct(LoggerInterface $logger, Database $dba, Account $mstdnAccountFactory, Status $mstdnStatusFactoryFactory)
|
||||
{
|
||||
$notification = DBA::selectFirst('notify', [], ['id' => $id]);
|
||||
if (!DBA::isResult($notification)) {
|
||||
parent::__construct($logger);
|
||||
$this->dba = $dba;
|
||||
$this->mstdnAccountFactory = $mstdnAccountFactory;
|
||||
$this->mstdnStatusFactory = $mstdnStatusFactoryFactory;
|
||||
}
|
||||
|
||||
public function createFromNotificationId(int $id)
|
||||
{
|
||||
$notification = $this->dba->selectFirst('notification', [], ['id' => $id]);
|
||||
if (!$this->dba->isResult($notification)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$cid = Contact::getIdForURL($notification['url'], 0, false);
|
||||
if (empty($cid)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
follow = Someone followed you
|
||||
follow_request = Someone requested to follow you
|
||||
|
|
@ -51,32 +62,30 @@ class Notification extends BaseFactory
|
|||
status = Someone you enabled notifications for has posted a status
|
||||
*/
|
||||
|
||||
switch ($notification['type']) {
|
||||
case ModelNotification\Type::INTRO:
|
||||
$type = 'follow_request';
|
||||
break;
|
||||
|
||||
case ModelNotification\Type::WALL:
|
||||
case ModelNotification\Type::COMMENT:
|
||||
case ModelNotification\Type::MAIL:
|
||||
case ModelNotification\Type::TAG_SELF:
|
||||
case ModelNotification\Type::POKE:
|
||||
$type = 'mention';
|
||||
break;
|
||||
|
||||
case ModelNotification\Type::SHARE:
|
||||
$type = 'status';
|
||||
break;
|
||||
|
||||
default:
|
||||
return null;
|
||||
if (($notification['vid'] == Verb::getID(Activity::FOLLOW)) && ($notification['type'] == Post\UserNotification::NOTIF_NONE)) {
|
||||
$contact = Contact::getById($notification['actor-id'], ['pending']);
|
||||
$type = $contact['pending'] ? $type = 'follow_request' : 'follow';
|
||||
} elseif (($notification['vid'] == Verb::getID(Activity::ANNOUNCE)) &&
|
||||
in_array($notification['type'], [Post\UserNotification::NOTIF_DIRECT_COMMENT, Post\UserNotification::NOTIF_DIRECT_THREAD_COMMENT])) {
|
||||
$type = 'reblog';
|
||||
} elseif (in_array($notification['vid'], [Verb::getID(Activity::LIKE), Verb::getID(Activity::DISLIKE)]) &&
|
||||
in_array($notification['type'], [Post\UserNotification::NOTIF_DIRECT_COMMENT, Post\UserNotification::NOTIF_DIRECT_THREAD_COMMENT])) {
|
||||
$type = 'favourite';
|
||||
} elseif ($notification['type'] == Post\UserNotification::NOTIF_SHARED) {
|
||||
$type = 'status';
|
||||
} elseif (in_array($notification['type'], [Post\UserNotification::NOTIF_EXPLICIT_TAGGED,
|
||||
Post\UserNotification::NOTIF_IMPLICIT_TAGGED, Post\UserNotification::NOTIF_DIRECT_COMMENT,
|
||||
Post\UserNotification::NOTIF_DIRECT_THREAD_COMMENT, Post\UserNotification::NOTIF_THREAD_COMMENT])) {
|
||||
$type = 'mention';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
$account = DI::mstdnAccount()->createFromContactId($cid);
|
||||
$account = $this->mstdnAccountFactory->createFromContactId($notification['actor-id'], $notification['uid']);
|
||||
|
||||
if (!empty($notification['uri-id'])) {
|
||||
if (!empty($notification['target-uri-id'])) {
|
||||
try {
|
||||
$status = DI::mstdnStatus()->createFromUriId($notification['uri-id'], $notification['uid']);
|
||||
$status = $this->mstdnStatusFactory->createFromUriId($notification['target-uri-id'], $notification['uid']);
|
||||
} catch (\Throwable $th) {
|
||||
$status = null;
|
||||
}
|
||||
|
|
@ -84,6 +93,6 @@ class Notification extends BaseFactory
|
|||
$status = null;
|
||||
}
|
||||
|
||||
return new \Friendica\Object\Api\Mastodon\Notification($id, $type, $notification['date'], $account, $status);
|
||||
return new \Friendica\Object\Api\Mastodon\Notification($id, $type, $notification['created'], $account, $status);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
namespace Friendica\Factory\Api\Mastodon;
|
||||
|
||||
use Exception;
|
||||
use Friendica\Object\Api\Mastodon\Relationship as RelationshipEntity;
|
||||
use Friendica\BaseFactory;
|
||||
use Friendica\Model\Contact;
|
||||
|
|
@ -31,9 +32,9 @@ class Relationship extends BaseFactory
|
|||
* @param int $contactId Contact ID (public or user contact)
|
||||
* @param int $uid User ID
|
||||
* @return RelationshipEntity
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function createFromContactId(int $contactId, int $uid)
|
||||
public function createFromContactId(int $contactId, int $uid): RelationshipEntity
|
||||
{
|
||||
$cdata = Contact::getPublicAndUserContacID($contactId, $uid);
|
||||
if (!empty($cdata)) {
|
||||
|
|
|
|||
|
|
@ -21,46 +21,59 @@
|
|||
|
||||
namespace Friendica\Factory\Api\Mastodon;
|
||||
|
||||
use Friendica\App\BaseURL;
|
||||
use Friendica\BaseFactory;
|
||||
use Friendica\Content\ContactSelector;
|
||||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Model\Verb;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Protocol\Activity;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Repository\ProfileField;
|
||||
use ImagickException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class Status extends BaseFactory
|
||||
{
|
||||
/** @var BaseURL */
|
||||
protected $baseUrl;
|
||||
/** @var ProfileField */
|
||||
protected $profileField;
|
||||
/** @var Field */
|
||||
protected $mstdnField;
|
||||
/** @var Database */
|
||||
private $dba;
|
||||
/** @var Account */
|
||||
private $mstdnAccountFactory;
|
||||
/** @var Mention */
|
||||
private $mstdnMentionFactory;
|
||||
/** @var Tag */
|
||||
private $mstdnTagFactory;
|
||||
/** @var Card */
|
||||
private $mstdnCardFactory;
|
||||
/** @var Attachment */
|
||||
private $mstdnAttachementFactory;
|
||||
/** @var Error */
|
||||
private $mstdnErrorFactory;
|
||||
|
||||
public function __construct(LoggerInterface $logger, BaseURL $baseURL, ProfileField $profileField, Field $mstdnField)
|
||||
public function __construct(LoggerInterface $logger, Database $dba,
|
||||
Account $mstdnAccountFactory, Mention $mstdnMentionFactory,
|
||||
Tag $mstdnTagFactory, Card $mstdnCardFactory,
|
||||
Attachment $mstdnAttachementFactory, Error $mstdnErrorFactory)
|
||||
{
|
||||
parent::__construct($logger);
|
||||
|
||||
$this->baseUrl = $baseURL;
|
||||
$this->profileField = $profileField;
|
||||
$this->mstdnField = $mstdnField;
|
||||
$this->dba = $dba;
|
||||
$this->mstdnAccountFactory = $mstdnAccountFactory;
|
||||
$this->mstdnMentionFactory = $mstdnMentionFactory;
|
||||
$this->mstdnTagFactory = $mstdnTagFactory;
|
||||
$this->mstdnCardFactory = $mstdnCardFactory;
|
||||
$this->mstdnAttachementFactory = $mstdnAttachementFactory;
|
||||
$this->mstdnErrorFactory = $mstdnErrorFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $uriId Uri-ID of the item
|
||||
* @param int $uid Item user
|
||||
*
|
||||
* @return \Friendica\Object\Api\Mastodon\Status
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
* @throws ImagickException|HTTPException\NotFoundException
|
||||
*/
|
||||
public function createFromUriId(int $uriId, $uid = 0)
|
||||
public function createFromUriId(int $uriId, $uid = 0): \Friendica\Object\Api\Mastodon\Status
|
||||
{
|
||||
$fields = ['uri-id', 'uid', 'author-id', 'author-link', 'starred', 'app', 'title', 'body', 'raw-body', 'created', 'network',
|
||||
'thr-parent-id', 'parent-author-id', 'language', 'uri', 'plink', 'private', 'vid', 'gravity'];
|
||||
|
|
@ -69,29 +82,53 @@ class Status extends BaseFactory
|
|||
throw new HTTPException\NotFoundException('Item with URI ID ' . $uriId . 'not found' . ($uid ? ' for user ' . $uid : '.'));
|
||||
}
|
||||
|
||||
$account = DI::mstdnAccount()->createFromContactId($item['author-id']);
|
||||
$account = $this->mstdnAccountFactory->createFromContactId($item['author-id']);
|
||||
|
||||
$counts = new \Friendica\Object\Api\Mastodon\Status\Counts(
|
||||
Post::count(['thr-parent-id' => $uriId, 'gravity' => GRAVITY_COMMENT, 'deleted' => false], [], false),
|
||||
Post::count(['thr-parent-id' => $uriId, 'gravity' => GRAVITY_ACTIVITY, 'vid' => Verb::getID(Activity::ANNOUNCE), 'deleted' => false], [], false),
|
||||
Post::count(['thr-parent-id' => $uriId, 'gravity' => GRAVITY_ACTIVITY, 'vid' => Verb::getID(Activity::LIKE), 'deleted' => false], [], false)
|
||||
Post::countPosts(['thr-parent-id' => $uriId, 'gravity' => GRAVITY_COMMENT, 'deleted' => false], []),
|
||||
Post::countPosts([
|
||||
'thr-parent-id' => $uriId,
|
||||
'gravity' => GRAVITY_ACTIVITY,
|
||||
'vid' => Verb::getID(Activity::ANNOUNCE),
|
||||
'deleted' => false
|
||||
], []),
|
||||
Post::countPosts([
|
||||
'thr-parent-id' => $uriId,
|
||||
'gravity' => GRAVITY_ACTIVITY,
|
||||
'vid' => Verb::getID(Activity::LIKE),
|
||||
'deleted' => false
|
||||
], [])
|
||||
);
|
||||
|
||||
$userAttributes = new \Friendica\Object\Api\Mastodon\Status\UserAttributes(
|
||||
Post::exists(['thr-parent-id' => $uriId, 'uid' => $uid, 'origin' => true, 'gravity' => GRAVITY_ACTIVITY, 'vid' => Verb::getID(Activity::LIKE), 'deleted' => false]),
|
||||
Post::exists(['thr-parent-id' => $uriId, 'uid' => $uid, 'origin' => true, 'gravity' => GRAVITY_ACTIVITY, 'vid' => Verb::getID(Activity::ANNOUNCE), 'deleted' => false]),
|
||||
Post::exists([
|
||||
'thr-parent-id' => $uriId,
|
||||
'uid' => $uid,
|
||||
'origin' => true,
|
||||
'gravity' => GRAVITY_ACTIVITY,
|
||||
'vid' => Verb::getID(Activity::LIKE)
|
||||
, 'deleted' => false
|
||||
]),
|
||||
Post::exists([
|
||||
'thr-parent-id' => $uriId,
|
||||
'uid' => $uid,
|
||||
'origin' => true,
|
||||
'gravity' => GRAVITY_ACTIVITY,
|
||||
'vid' => Verb::getID(Activity::ANNOUNCE),
|
||||
'deleted' => false
|
||||
]),
|
||||
Post\ThreadUser::getIgnored($uriId, $uid),
|
||||
(bool)$item['starred'],
|
||||
(bool)($item['starred'] && ($item['gravity'] == GRAVITY_PARENT)),
|
||||
Post\ThreadUser::getPinned($uriId, $uid)
|
||||
);
|
||||
|
||||
$sensitive = DBA::exists('tag-view', ['uri-id' => $uriId, 'name' => 'nsfw']);
|
||||
$sensitive = $this->dba->exists('tag-view', ['uri-id' => $uriId, 'name' => 'nsfw']);
|
||||
$application = new \Friendica\Object\Api\Mastodon\Application($item['app'] ?: ContactSelector::networkToName($item['network'], $item['author-link']));
|
||||
|
||||
$mentions = DI::mstdnMention()->createFromUriId($uriId);
|
||||
$tags = DI::mstdnTag()->createFromUriId($uriId);
|
||||
$card = DI::mstdnCard()->createFromUriId($uriId);
|
||||
$attachments = DI::mstdnAttachment()->createFromUriId($uriId);
|
||||
$mentions = $this->mstdnMentionFactory->createFromUriId($uriId)->getArrayCopy();
|
||||
$tags = $this->mstdnTagFactory->createFromUriId($uriId);
|
||||
$card = $this->mstdnCardFactory->createFromUriId($uriId);
|
||||
$attachments = $this->mstdnAttachementFactory->createFromUriId($uriId);
|
||||
|
||||
$shared = BBCode::fetchShareAttributes($item['body']);
|
||||
if (!empty($shared['guid'])) {
|
||||
|
|
@ -99,21 +136,21 @@ class Status extends BaseFactory
|
|||
|
||||
$shared_uri_id = $shared_item['uri-id'] ?? 0;
|
||||
|
||||
$mentions = array_merge($mentions, DI::mstdnMention()->createFromUriId($shared_uri_id));
|
||||
$tags = array_merge($tags, DI::mstdnTag()->createFromUriId($shared_uri_id));
|
||||
$attachments = array_merge($attachments, DI::mstdnAttachment()->createFromUriId($shared_uri_id));
|
||||
$mentions = array_merge($mentions, $this->mstdnMentionFactory->createFromUriId($shared_uri_id)->getArrayCopy());
|
||||
$tags = array_merge($tags, $this->mstdnTagFactory->createFromUriId($shared_uri_id));
|
||||
$attachments = array_merge($attachments, $this->mstdnAttachementFactory->createFromUriId($shared_uri_id));
|
||||
|
||||
if (empty($card->toArray())) {
|
||||
$card = DI::mstdnCard()->createFromUriId($shared_uri_id);
|
||||
$card = $this->mstdnCardFactory->createFromUriId($shared_uri_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($item['vid'] == Verb::getID(Activity::ANNOUNCE)) {
|
||||
$reshare = $this->createFromUriId($item['thr-parent-id'], $uid)->toArray();
|
||||
$reshared_item = Post::selectFirst(['title', 'body'], ['uri-id' => $item['thr-parent-id'], 'uid' => [0, $uid]]);
|
||||
$reshare = $this->createFromUriId($item['thr-parent-id'], $uid)->toArray();
|
||||
$reshared_item = Post::selectFirst(['title', 'body'], ['uri-id' => $item['thr-parent-id'],'uid' => [0, $uid]]);
|
||||
$item['title'] = $reshared_item['title'] ?? $item['title'];
|
||||
$item['body'] = $reshared_item['body'] ?? $item['body'];
|
||||
$item['body'] = $reshared_item['body'] ?? $item['body'];
|
||||
} else {
|
||||
$reshare = [];
|
||||
}
|
||||
|
|
@ -123,20 +160,23 @@ class Status extends BaseFactory
|
|||
|
||||
/**
|
||||
* @param int $uriId id of the mail
|
||||
*
|
||||
* @return \Friendica\Object\Api\Mastodon\Status
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
* @throws ImagickException|HTTPException\NotFoundException
|
||||
*/
|
||||
public function createFromMailId(int $id)
|
||||
public function createFromMailId(int $id): \Friendica\Object\Api\Mastodon\Status
|
||||
{
|
||||
$item = ActivityPub\Transmitter::ItemArrayFromMail($id, true);
|
||||
if (empty($item)) {
|
||||
DI::mstdnError()->RecordNotFound();
|
||||
$this->mstdnErrorFactory->RecordNotFound();
|
||||
}
|
||||
|
||||
$account = DI::mstdnAccount()->createFromContactId($item['author-id']);
|
||||
$account = $this->mstdnAccountFactory->createFromContactId($item['author-id']);
|
||||
|
||||
$counts = new \Friendica\Object\Api\Mastodon\Status\Counts(0, 0, 0);
|
||||
$replies = $this->dba->count('mail', ['thr-parent-id' => $item['uri-id'], 'reply' => true]);
|
||||
|
||||
$counts = new \Friendica\Object\Api\Mastodon\Status\Counts($replies, 0, 0);
|
||||
|
||||
$userAttributes = new \Friendica\Object\Api\Mastodon\Status\UserAttributes(false, false, false, false, false);
|
||||
|
||||
|
|
|
|||
|
|
@ -25,39 +25,31 @@ use Friendica\App\BaseURL;
|
|||
use Friendica\BaseFactory;
|
||||
use Friendica\Model\Tag as TagModel;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Repository\ProfileField;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class Tag extends BaseFactory
|
||||
{
|
||||
/** @var BaseURL */
|
||||
protected $baseUrl;
|
||||
/** @var ProfileField */
|
||||
protected $profileField;
|
||||
/** @var Field */
|
||||
protected $mstdnField;
|
||||
private $baseUrl;
|
||||
|
||||
public function __construct(LoggerInterface $logger, BaseURL $baseURL, ProfileField $profileField, Field $mstdnField)
|
||||
public function __construct(LoggerInterface $logger, BaseURL $baseURL)
|
||||
{
|
||||
parent::__construct($logger);
|
||||
|
||||
$this->baseUrl = $baseURL;
|
||||
$this->profileField = $profileField;
|
||||
$this->mstdnField = $mstdnField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $uriId Uri-ID of the item
|
||||
* @return array
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public function createFromUriId(int $uriId)
|
||||
public function createFromUriId(int $uriId): array
|
||||
{
|
||||
$hashtags = [];
|
||||
$tags = TagModel::getByURIId($uriId, [TagModel::HASHTAG]);
|
||||
$tags = TagModel::getByURIId($uriId, [TagModel::HASHTAG]);
|
||||
foreach ($tags as $tag) {
|
||||
$hashtag = new \Friendica\Object\Api\Mastodon\Tag($this->baseUrl, $tag);
|
||||
$hashtag = new \Friendica\Object\Api\Mastodon\Tag($this->baseUrl, $tag);
|
||||
$hashtags[] = $hashtag->toArray();
|
||||
}
|
||||
return $hashtags;
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ class Introduction extends BaseFactory
|
|||
'madeby_zrl' => Contact::magicLink($notification['url']),
|
||||
'madeby_addr' => $notification['addr'],
|
||||
'contact_id' => $notification['contact-id'],
|
||||
'photo' => (!empty($notification['fphoto']) ? Proxy::proxifyUrl($notification['fphoto'], false, Proxy::SIZE_SMALL) : Contact::DEFAULT_AVATAR_PHOTO),
|
||||
'photo' => Contact::getAvatarUrlForUrl($notification['furl'], 0, Proxy::SIZE_SMALL),
|
||||
'name' => $notification['fname'],
|
||||
'url' => $notification['furl'],
|
||||
'zrl' => Contact::magicLink($notification['furl']),
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ use Friendica\Core\PConfig\IPConfig;
|
|||
use Friendica\Core\Protocol;
|
||||
use Friendica\Core\Session\ISession;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Module\BaseNotifications;
|
||||
use Friendica\Network\HTTPException\InternalServerErrorException;
|
||||
|
|
@ -239,7 +240,7 @@ class Notification extends BaseFactory
|
|||
$formattedNotifications[] = new \Friendica\Object\Notification\Notification([
|
||||
'label' => 'notification',
|
||||
'link' => $this->baseUrl->get(true) . '/notification/' . $notification->id,
|
||||
'image' => Proxy::proxifyUrl($notification->photo, false, Proxy::SIZE_MICRO),
|
||||
'image' => Contact::getAvatarUrlForUrl($notification->url, $notification->uid, Proxy::SIZE_MICRO),
|
||||
'url' => $notification->url,
|
||||
'text' => strip_tags(BBCode::convert($notification->msg)),
|
||||
'when' => DateTimeFormat::local($notification->date, 'r'),
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ use Friendica\Core\Cache\Duration;
|
|||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Database\DBStructure;
|
||||
use Friendica\DI;
|
||||
use Friendica\Network\Probe;
|
||||
use Friendica\Protocol\ActivityNamespace;
|
||||
|
|
@ -210,6 +211,11 @@ class APContact
|
|||
$apcontact['photo'] = JsonLD::fetchElement($compacted['as:icon'], 'as:url', '@id');
|
||||
}
|
||||
|
||||
$apcontact['header'] = JsonLD::fetchElement($compacted, 'as:image', '@id');
|
||||
if (is_array($apcontact['header']) || !empty($compacted['as:image']['as:url']['@id'])) {
|
||||
$apcontact['header'] = JsonLD::fetchElement($compacted['as:image'], 'as:url', '@id');
|
||||
}
|
||||
|
||||
if (empty($apcontact['alias'])) {
|
||||
$apcontact['alias'] = JsonLD::fetchElement($compacted, 'as:url', '@id');
|
||||
if (is_array($apcontact['alias'])) {
|
||||
|
|
@ -278,6 +284,8 @@ class APContact
|
|||
}
|
||||
}
|
||||
|
||||
$apcontact['discoverable'] = JsonLD::fetchElement($compacted, 'toot:discoverable', '@value');
|
||||
|
||||
// To-Do
|
||||
|
||||
// Unhandled
|
||||
|
|
@ -349,6 +357,9 @@ class APContact
|
|||
DBA::delete('apcontact', ['url' => $url]);
|
||||
}
|
||||
|
||||
// Limit the length on incoming fields
|
||||
$apcontact = DBStructure::getFieldsForTable('apcontact', $apcontact);
|
||||
|
||||
if (DBA::exists('apcontact', ['url' => $apcontact['url']])) {
|
||||
DBA::update('apcontact', $apcontact, ['url' => $apcontact['url']]);
|
||||
} else {
|
||||
|
|
@ -357,7 +368,7 @@ class APContact
|
|||
|
||||
Logger::info('Updated profile', ['url' => $url]);
|
||||
|
||||
return $apcontact;
|
||||
return DBA::selectFirst('apcontact', [], ['url' => $apcontact['url']]) ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -271,7 +271,7 @@ class Contact
|
|||
|
||||
// Update the contact in the background if needed
|
||||
$updated = max($contact['success_update'], $contact['created'], $contact['updated'], $contact['last-update'], $contact['failure_update']);
|
||||
if (($updated < DateTimeFormat::utc('now -7 days')) && in_array($contact['network'], Protocol::FEDERATED)) {
|
||||
if (($updated < DateTimeFormat::utc('now -7 days')) && in_array($contact['network'], Protocol::FEDERATED) && !self::isLocalById($contact['id'])) {
|
||||
Worker::add(PRIORITY_LOW, "UpdateContact", $contact['id']);
|
||||
}
|
||||
|
||||
|
|
@ -566,18 +566,13 @@ class Contact
|
|||
*/
|
||||
public static function createSelfFromUserId($uid)
|
||||
{
|
||||
// Only create the entry if it doesn't exist yet
|
||||
if (DBA::exists('contact', ['uid' => $uid, 'self' => true])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$user = DBA::selectFirst('user', ['uid', 'username', 'nickname', 'pubkey', 'prvkey'],
|
||||
['uid' => $uid, 'account_expired' => false]);
|
||||
if (!DBA::isResult($user)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$return = DBA::insert('contact', [
|
||||
$contact = [
|
||||
'uid' => $user['uid'],
|
||||
'created' => DateTimeFormat::utcNow(),
|
||||
'self' => 1,
|
||||
|
|
@ -602,7 +597,23 @@ class Contact
|
|||
'uri-date' => DateTimeFormat::utcNow(),
|
||||
'avatar-date' => DateTimeFormat::utcNow(),
|
||||
'closeness' => 0
|
||||
]);
|
||||
];
|
||||
|
||||
$return = true;
|
||||
|
||||
// Only create the entry if it doesn't exist yet
|
||||
if (!DBA::exists('contact', ['uid' => $uid, 'self' => true])) {
|
||||
$return = DBA::insert('contact', $contact);
|
||||
}
|
||||
|
||||
// Create the public contact
|
||||
if (!DBA::exists('contact', ['nurl' => $contact['nurl'], 'uid' => 0])) {
|
||||
$contact['self'] = false;
|
||||
$contact['uid'] = 0;
|
||||
$contact['prvkey'] = null;
|
||||
|
||||
DBA::insert('contact', $contact, Database::INSERT_IGNORE);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
|
@ -612,29 +623,30 @@ class Contact
|
|||
*
|
||||
* @param int $uid
|
||||
* @param boolean $update_avatar Force the avatar update
|
||||
* @return bool "true" if updated
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function updateSelfFromUserID($uid, $update_avatar = false)
|
||||
{
|
||||
$fields = ['id', 'name', 'nick', 'location', 'about', 'keywords', 'avatar', 'prvkey', 'pubkey',
|
||||
'xmpp', 'contact-type', 'forum', 'prv', 'avatar-date', 'url', 'nurl', 'unsearchable',
|
||||
'photo', 'thumb', 'micro', 'addr', 'request', 'notify', 'poll', 'confirm', 'poco'];
|
||||
'photo', 'thumb', 'micro', 'addr', 'request', 'notify', 'poll', 'confirm', 'poco', 'network'];
|
||||
$self = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]);
|
||||
if (!DBA::isResult($self)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
$fields = ['nickname', 'page-flags', 'account-type', 'prvkey', 'pubkey'];
|
||||
$user = DBA::selectFirst('user', $fields, ['uid' => $uid, 'account_expired' => false]);
|
||||
if (!DBA::isResult($user)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
$fields = ['name', 'photo', 'thumb', 'about', 'address', 'locality', 'region',
|
||||
'country-name', 'pub_keywords', 'xmpp', 'net-publish'];
|
||||
$profile = DBA::selectFirst('profile', $fields, ['uid' => $uid]);
|
||||
if (!DBA::isResult($profile)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
$file_suffix = 'jpg';
|
||||
|
|
@ -643,7 +655,7 @@ class Contact
|
|||
'avatar-date' => $self['avatar-date'], 'location' => Profile::formatLocation($profile),
|
||||
'about' => $profile['about'], 'keywords' => $profile['pub_keywords'],
|
||||
'contact-type' => $user['account-type'], 'prvkey' => $user['prvkey'],
|
||||
'pubkey' => $user['pubkey'], 'xmpp' => $profile['xmpp']];
|
||||
'pubkey' => $user['pubkey'], 'xmpp' => $profile['xmpp'], 'network' => Protocol::DFRN];
|
||||
|
||||
// it seems as if ported accounts can have wrong values, so we make sure that now everything is fine.
|
||||
$fields['url'] = DI::baseUrl() . '/profile/' . $user['nickname'];
|
||||
|
|
@ -704,6 +716,8 @@ class Contact
|
|||
DBA::update('contact', $fields, ['id' => $self['id']]);
|
||||
|
||||
// Update the public contact as well
|
||||
$fields['prvkey'] = null;
|
||||
$fields['self'] = false;
|
||||
DBA::update('contact', $fields, ['uid' => 0, 'nurl' => $self['nurl']]);
|
||||
|
||||
// Update the profile
|
||||
|
|
@ -711,6 +725,8 @@ class Contact
|
|||
'thumb' => DI::baseUrl() . '/photo/avatar/' . $uid .'.' . $file_suffix];
|
||||
DBA::update('profile', $fields, ['uid' => $uid]);
|
||||
}
|
||||
|
||||
return $update;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1087,7 +1103,7 @@ class Contact
|
|||
if (($uid == 0) && (empty($data['network']) || ($data['network'] == Protocol::PHANTOM))) {
|
||||
// Fetch data for the public contact via the first found personal contact
|
||||
/// @todo Check if this case can happen at all (possibly with mail accounts?)
|
||||
$fields = ['name', 'nick', 'url', 'addr', 'alias', 'avatar', 'contact-type',
|
||||
$fields = ['name', 'nick', 'url', 'addr', 'alias', 'avatar', 'header', 'contact-type',
|
||||
'keywords', 'location', 'about', 'unsearchable', 'batch', 'notify', 'poll',
|
||||
'request', 'confirm', 'poco', 'subscribe', 'network', 'baseurl', 'gsid'];
|
||||
|
||||
|
|
@ -1485,15 +1501,15 @@ class Contact
|
|||
{
|
||||
if (!empty($contact)) {
|
||||
$contact = self::checkAvatarCacheByArray($contact, $no_update);
|
||||
if (!empty($contact[$field])) {
|
||||
$avatar = $contact[$field];
|
||||
if (!empty($contact['id'])) {
|
||||
return self::getAvatarUrlForId($contact['id'], $size, $contact['updated'] ?? '');
|
||||
} elseif (!empty($contact[$field])) {
|
||||
return $contact[$field];
|
||||
} elseif (!empty($contact['avatar'])) {
|
||||
$avatar = $contact['avatar'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($no_update && empty($avatar) && !empty($contact['avatar'])) {
|
||||
$avatar = $contact['avatar'];
|
||||
}
|
||||
|
||||
if (empty($avatar)) {
|
||||
$avatar = self::getDefaultAvatar([], $size);
|
||||
}
|
||||
|
|
@ -1598,7 +1614,7 @@ class Contact
|
|||
*
|
||||
* @param array $contact contact array
|
||||
* @param string $size Size of the avatar picture
|
||||
* @return void
|
||||
* @return string avatar URL
|
||||
*/
|
||||
public static function getDefaultAvatar(array $contact, string $size)
|
||||
{
|
||||
|
|
@ -1646,6 +1662,99 @@ class Contact
|
|||
return DI::baseUrl() . $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get avatar link for given contact id
|
||||
*
|
||||
* @param integer $cid contact id
|
||||
* @param string $size One of the ProxyUtils::SIZE_* constants
|
||||
* @param string $updated Contact update date
|
||||
* @return string avatar link
|
||||
*/
|
||||
public static function getAvatarUrlForId(int $cid, string $size = '', string $updated = ''):string
|
||||
{
|
||||
// We have to fetch the "updated" variable when it wasn't provided
|
||||
// The parameter can be provided to improve performance
|
||||
if (empty($updated)) {
|
||||
$contact = self::getById($cid, ['updated']);
|
||||
$updated = $contact['updated'] ?? '';
|
||||
}
|
||||
|
||||
$url = DI::baseUrl() . '/photo/contact/';
|
||||
switch ($size) {
|
||||
case Proxy::SIZE_MICRO:
|
||||
$url .= Proxy::PIXEL_MICRO . '/';
|
||||
break;
|
||||
case Proxy::SIZE_THUMB:
|
||||
$url .= Proxy::PIXEL_THUMB . '/';
|
||||
break;
|
||||
case Proxy::SIZE_SMALL:
|
||||
$url .= Proxy::PIXEL_SMALL . '/';
|
||||
break;
|
||||
case Proxy::SIZE_MEDIUM:
|
||||
$url .= Proxy::PIXEL_MEDIUM . '/';
|
||||
break;
|
||||
case Proxy::SIZE_LARGE:
|
||||
$url .= Proxy::PIXEL_LARGE . '/';
|
||||
break;
|
||||
}
|
||||
return $url . $cid . ($updated ? '?ts=' . strtotime($updated) : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get avatar link for given contact URL
|
||||
*
|
||||
* @param string $url contact url
|
||||
* @param integer $uid user id
|
||||
* @param string $size One of the ProxyUtils::SIZE_* constants
|
||||
* @return string avatar link
|
||||
*/
|
||||
public static function getAvatarUrlForUrl(string $url, int $uid, string $size = ''):string
|
||||
{
|
||||
$condition = ["`nurl` = ? AND ((`uid` = ? AND `network` IN (?, ?)) OR `uid` = ?)",
|
||||
Strings::normaliseLink($url), $uid, Protocol::FEED, Protocol::MAIL, 0];
|
||||
$contact = self::selectFirst(['id', 'updated'], $condition);
|
||||
return self::getAvatarUrlForId($contact['id'] ?? 0, $size, $contact['updated'] ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get header link for given contact id
|
||||
*
|
||||
* @param integer $cid contact id
|
||||
* @param string $size One of the ProxyUtils::SIZE_* constants
|
||||
* @param string $updated Contact update date
|
||||
* @return string header link
|
||||
*/
|
||||
public static function getHeaderUrlForId(int $cid, string $size = '', string $updated = ''):string
|
||||
{
|
||||
// We have to fetch the "updated" variable when it wasn't provided
|
||||
// The parameter can be provided to improve performance
|
||||
if (empty($updated)) {
|
||||
$contact = self::getById($cid, ['updated']);
|
||||
$updated = $contact['updated'] ?? '';
|
||||
}
|
||||
|
||||
$url = DI::baseUrl() . '/photo/header/';
|
||||
switch ($size) {
|
||||
case Proxy::SIZE_MICRO:
|
||||
$url .= Proxy::PIXEL_MICRO . '/';
|
||||
break;
|
||||
case Proxy::SIZE_THUMB:
|
||||
$url .= Proxy::PIXEL_THUMB . '/';
|
||||
break;
|
||||
case Proxy::SIZE_SMALL:
|
||||
$url .= Proxy::PIXEL_SMALL . '/';
|
||||
break;
|
||||
case Proxy::SIZE_MEDIUM:
|
||||
$url .= Proxy::PIXEL_MEDIUM . '/';
|
||||
break;
|
||||
case Proxy::SIZE_LARGE:
|
||||
$url .= Proxy::PIXEL_LARGE . '/';
|
||||
break;
|
||||
}
|
||||
|
||||
return $url . $cid . ($updated ? '?ts=' . strtotime($updated) : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the avatar links in a contact only if needed
|
||||
*
|
||||
|
|
@ -1933,14 +2042,23 @@ class Contact
|
|||
// These fields aren't updated by this routine:
|
||||
// 'xmpp', 'sensitive'
|
||||
|
||||
$fields = ['uid', 'avatar', 'name', 'nick', 'location', 'keywords', 'about', 'subscribe', 'manually-approve',
|
||||
'unsearchable', 'url', 'addr', 'batch', 'notify', 'poll', 'request', 'confirm', 'poco',
|
||||
$fields = ['uid', 'avatar', 'header', 'name', 'nick', 'location', 'keywords', 'about', 'subscribe',
|
||||
'manually-approve', 'unsearchable', 'url', 'addr', 'batch', 'notify', 'poll', 'request', 'confirm', 'poco',
|
||||
'network', 'alias', 'baseurl', 'gsid', 'forum', 'prv', 'contact-type', 'pubkey', 'last-item'];
|
||||
$contact = DBA::selectFirst('contact', $fields, ['id' => $id]);
|
||||
if (!DBA::isResult($contact)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (self::isLocal($ret['url'])) {
|
||||
if ($contact['uid'] == 0) {
|
||||
Logger::info('Local contacts are not updated here.');
|
||||
} else {
|
||||
self::updateFromPublicContact($id, $contact);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!empty($ret['account-type']) && $ret['account-type'] == User::ACCOUNT_TYPE_DELETED) {
|
||||
Logger::info('Deleted account', ['id' => $id, 'url' => $ret['url'], 'ret' => $ret]);
|
||||
self::remove($id);
|
||||
|
|
@ -2072,6 +2190,26 @@ class Contact
|
|||
return true;
|
||||
}
|
||||
|
||||
private static function updateFromPublicContact(int $id, array $contact)
|
||||
{
|
||||
$public = self::getByURL($contact['url'], false);
|
||||
|
||||
$fields = [];
|
||||
|
||||
foreach ($contact as $field => $value) {
|
||||
if ($field == 'uid') {
|
||||
continue;
|
||||
}
|
||||
if ($public[$field] != $value) {
|
||||
$fields[$field] = $public[$field];
|
||||
}
|
||||
}
|
||||
if (!empty($fields)) {
|
||||
DBA::update('contact', $fields, ['id' => $id, 'self' => false]);
|
||||
Logger::info('Updating local contact', ['id' => $id]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param integer $url contact url
|
||||
* @return integer Contact id
|
||||
|
|
@ -2180,8 +2318,10 @@ class Contact
|
|||
}
|
||||
|
||||
if (!empty($arr['contact']['name'])) {
|
||||
$probed = false;
|
||||
$ret = $arr['contact'];
|
||||
} else {
|
||||
$probed = true;
|
||||
$ret = Probe::uri($url, $network, $user['uid']);
|
||||
}
|
||||
|
||||
|
|
@ -2325,6 +2465,10 @@ class Contact
|
|||
// pull feed and consume it, which should subscribe to the hub.
|
||||
if ($contact['network'] == Protocol::OSTATUS) {
|
||||
Worker::add(PRIORITY_HIGH, 'OnePoll', $contact_id, 'force');
|
||||
}
|
||||
|
||||
if ($probed) {
|
||||
self::updateFromProbeArray($contact_id, $ret);
|
||||
} else {
|
||||
Worker::add(PRIORITY_HIGH, 'UpdateContact', $contact_id);
|
||||
}
|
||||
|
|
@ -2518,6 +2662,8 @@ class Contact
|
|||
// Ensure to always have the correct network type, independent from the connection request method
|
||||
self::updateFromProbe($contact['id']);
|
||||
|
||||
Post\UserNotification::insertNotication($contact['id'], Verb::getID(Activity::FOLLOW), $importer['uid']);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
// send email notification to owner?
|
||||
|
|
@ -2549,6 +2695,8 @@ class Contact
|
|||
|
||||
self::updateAvatar($contact_id, $photo, true);
|
||||
|
||||
Post\UserNotification::insertNotication($contact_id, Verb::getID(Activity::FOLLOW), $importer['uid']);
|
||||
|
||||
$contact_record = DBA::selectFirst('contact', ['id', 'network', 'name', 'url', 'photo'], ['id' => $contact_id]);
|
||||
|
||||
/// @TODO Encapsulate this into a function/method
|
||||
|
|
|
|||
|
|
@ -78,19 +78,22 @@ class Relation
|
|||
{
|
||||
$contact = Contact::getByURL($url);
|
||||
if (empty($contact)) {
|
||||
Logger::info('Contact not found', ['url' => $url]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self::isDiscoverable($url, $contact)) {
|
||||
Logger::info('Contact is not discoverable', ['url' => $url]);
|
||||
return;
|
||||
}
|
||||
|
||||
$uid = User::getIdForURL($url);
|
||||
if (!empty($uid)) {
|
||||
// Fetch the followers/followings locally
|
||||
Logger::info('Fetch the followers/followings locally', ['url' => $url]);
|
||||
$followers = self::getContacts($uid, [Contact::FOLLOWER, Contact::FRIEND]);
|
||||
$followings = self::getContacts($uid, [Contact::SHARING, Contact::FRIEND]);
|
||||
} else {
|
||||
} elseif (!Contact::isLocal($url)) {
|
||||
Logger::info('Fetch the followers/followings by polling the endpoints', ['url' => $url]);
|
||||
$apcontact = APContact::getByURL($url, false);
|
||||
|
||||
if (!empty($apcontact['followers']) && is_string($apcontact['followers'])) {
|
||||
|
|
@ -104,6 +107,10 @@ class Relation
|
|||
} else {
|
||||
$followings = [];
|
||||
}
|
||||
} else {
|
||||
Logger::notice('Contact seems to be local but could not be found here', ['url' => $url]);
|
||||
$followers = [];
|
||||
$followings = [];
|
||||
}
|
||||
|
||||
if (empty($followers) && empty($followings)) {
|
||||
|
|
|
|||
|
|
@ -586,10 +586,10 @@ class Event
|
|||
$last_date = '';
|
||||
$fmt = DI::l10n()->t('l, F j');
|
||||
foreach ($event_result as $event) {
|
||||
$item = Post::selectFirst(['plink', 'author-name', 'author-avatar', 'author-link'], ['id' => $event['itemid']]);
|
||||
$item = Post::selectFirst(['plink', 'author-name', 'author-avatar', 'author-link', 'private'], ['id' => $event['itemid']]);
|
||||
if (!DBA::isResult($item)) {
|
||||
// Using default values when no item had been found
|
||||
$item = ['plink' => '', 'author-name' => '', 'author-avatar' => '', 'author-link' => ''];
|
||||
$item = ['plink' => '', 'author-name' => '', 'author-avatar' => '', 'author-link' => '', 'private' => Item::PUBLIC];
|
||||
}
|
||||
|
||||
$event = array_merge($event, $item);
|
||||
|
|
|
|||
|
|
@ -802,6 +802,7 @@ class GServer
|
|||
/**
|
||||
* Parses Nodeinfo 2
|
||||
*
|
||||
* @see https://git.feneas.org/jaywink/nodeinfo2
|
||||
* @param string $nodeinfo_url address of the nodeinfo path
|
||||
* @return array Server data
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
|
|
@ -850,7 +851,9 @@ class GServer
|
|||
if (!empty($nodeinfo['protocols'])) {
|
||||
$protocols = [];
|
||||
foreach ($nodeinfo['protocols'] as $protocol) {
|
||||
$protocols[$protocol] = true;
|
||||
if (is_string($protocol)) {
|
||||
$protocols[$protocol] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($protocols['dfrn'])) {
|
||||
|
|
|
|||
|
|
@ -159,6 +159,10 @@ class Item
|
|||
$fields['vid'] = Verb::getID($fields['verb']);
|
||||
}
|
||||
|
||||
if (!empty($fields['edited'])) {
|
||||
$previous = Post::selectFirst(['edited'], $condition);
|
||||
}
|
||||
|
||||
$rows = Post::update($fields, $condition);
|
||||
if (is_bool($rows)) {
|
||||
return $rows;
|
||||
|
|
@ -180,16 +184,18 @@ class Item
|
|||
if (!empty($fields['body'])) {
|
||||
Post\Media::insertFromAttachmentData($item['uri-id'], $fields['body']);
|
||||
|
||||
if ($item['author-network'] != Protocol::DFRN) {
|
||||
Post\Media::insertFromRelevantUrl($item['uri-id'], $fields['body']);
|
||||
}
|
||||
|
||||
$content_fields = ['raw-body' => trim($fields['raw-body'] ?? $fields['body'])];
|
||||
|
||||
// Remove all media attachments from the body and store them in the post-media table
|
||||
// @todo On shared postings (Diaspora style and commented reshare) don't fetch content from the shared part
|
||||
$content_fields['raw-body'] = Post\Media::insertFromBody($item['uri-id'], $content_fields['raw-body']);
|
||||
$content_fields['raw-body'] = self::setHashtags($content_fields['raw-body']);
|
||||
|
||||
if ($item['author-network'] != Protocol::DFRN) {
|
||||
Post\Media::insertFromRelevantUrl($item['uri-id'], $content_fields['raw-body']);
|
||||
}
|
||||
|
||||
Post\Content::update($item['uri-id'], $content_fields);
|
||||
}
|
||||
|
||||
if (!empty($fields['file'])) {
|
||||
|
|
@ -201,8 +207,8 @@ class Item
|
|||
}
|
||||
|
||||
// We only need to notfiy others when it is an original entry from us.
|
||||
// Only call the notifier when the item has some content relevant change.
|
||||
if ($item['origin'] && in_array('edited', array_keys($fields))) {
|
||||
// Only call the notifier when the item had been edited and records had been changed.
|
||||
if ($item['origin'] && !empty($fields['edited']) && ($previous['edited'] != $fields['edited'])) {
|
||||
$notify_items[] = $item['id'];
|
||||
}
|
||||
}
|
||||
|
|
@ -516,7 +522,7 @@ class Item
|
|||
public static function isValid(array $item)
|
||||
{
|
||||
// When there is no content then we don't post it
|
||||
if (($item['body'] . $item['title'] == '') && !Post\Media::existsByURIId($item['uri-id'])) {
|
||||
if (($item['body'] . $item['title'] == '') && (empty($item['uri-id']) || !Post\Media::existsByURIId($item['uri-id']))) {
|
||||
Logger::notice('No body, no title.');
|
||||
return false;
|
||||
}
|
||||
|
|
@ -991,14 +997,14 @@ class Item
|
|||
|
||||
Post\Media::insertFromAttachmentData($item['uri-id'], $item['body']);
|
||||
|
||||
if (!DBA::exists('contact', ['id' => $item['author-id'], 'network' => Protocol::DFRN])) {
|
||||
Post\Media::insertFromRelevantUrl($item['uri-id'], $item['body']);
|
||||
}
|
||||
|
||||
// Remove all media attachments from the body and store them in the post-media table
|
||||
$item['raw-body'] = Post\Media::insertFromBody($item['uri-id'], $item['raw-body']);
|
||||
$item['raw-body'] = self::setHashtags($item['raw-body']);
|
||||
|
||||
if (!DBA::exists('contact', ['id' => $item['author-id'], 'network' => Protocol::DFRN])) {
|
||||
Post\Media::insertFromRelevantUrl($item['uri-id'], $item['raw-body']);
|
||||
}
|
||||
|
||||
// Check for hashtags in the body and repair or add hashtag links
|
||||
$item['body'] = self::setHashtags($item['body']);
|
||||
|
||||
|
|
@ -1018,6 +1024,30 @@ class Item
|
|||
|
||||
if (empty($item['event-id'])) {
|
||||
unset($item['event-id']);
|
||||
|
||||
$ev = Event::fromBBCode($item['body']);
|
||||
if ((!empty($ev['desc']) || !empty($ev['summary'])) && !empty($ev['start'])) {
|
||||
Logger::info('Event found.');
|
||||
$ev['cid'] = $item['contact-id'];
|
||||
$ev['uid'] = $item['uid'];
|
||||
$ev['uri'] = $item['uri'];
|
||||
$ev['edited'] = $item['edited'];
|
||||
$ev['private'] = $item['private'];
|
||||
$ev['guid'] = $item['guid'];
|
||||
$ev['plink'] = $item['plink'];
|
||||
$ev['network'] = $item['network'];
|
||||
$ev['protocol'] = $item['protocol'];
|
||||
$ev['direction'] = $item['direction'];
|
||||
$ev['source'] = $item['source'];
|
||||
|
||||
$event = DBA::selectFirst('event', ['id'], ['uri' => $item['uri'], 'uid' => $item['uid']]);
|
||||
if (DBA::isResult($event)) {
|
||||
$ev['id'] = $event['id'];
|
||||
}
|
||||
|
||||
$item['event-id'] = Event::store($ev);
|
||||
Logger::info('Event was stored', ['id' => $item['event-id']]);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($item['causer-id'])) {
|
||||
|
|
@ -1034,7 +1064,14 @@ class Item
|
|||
Post\Content::insert($item['uri-id'], $item);
|
||||
}
|
||||
|
||||
// Diaspora signature
|
||||
// Create Diaspora signature
|
||||
if ($item['origin'] && empty($item['diaspora_signed_text']) && ($item['gravity'] != GRAVITY_PARENT)) {
|
||||
$signed = Diaspora::createCommentSignature($uid, $item);
|
||||
if (!empty($signed)) {
|
||||
$item['diaspora_signed_text'] = json_encode($signed);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($item['diaspora_signed_text'])) {
|
||||
DBA::replace('diaspora-interaction', ['uri-id' => $item['uri-id'], 'interaction' => $item['diaspora_signed_text']]);
|
||||
}
|
||||
|
|
@ -1194,13 +1231,10 @@ class Item
|
|||
Logger::info('The resharer is no forum: quit', ['resharer' => $item['author-id'], 'owner' => $parent['owner-id'], 'author' => $parent['author-id'], 'uid' => $item['uid']]);
|
||||
return;
|
||||
}
|
||||
self::update(['post-reason' => self::PR_ANNOUNCEMENT, 'causer-id' => $item['author-id']], ['id' => $parent['id']]);
|
||||
Logger::info('Set announcement post-reason', ['uri-id' => $item['uri-id'], 'thr-parent-id' => $item['thr-parent-id'], 'uid' => $item['uid']]);
|
||||
return;
|
||||
}
|
||||
|
||||
self::update(['owner-id' => $item['author-id'], 'contact-id' => $cid], ['id' => $parent['id']]);
|
||||
Logger::info('Change owner of the parent', ['uri-id' => $item['uri-id'], 'thr-parent-id' => $item['thr-parent-id'], 'uid' => $item['uid'], 'owner-id' => $item['author-id'], 'contact-id' => $cid]);
|
||||
self::update(['post-reason' => self::PR_ANNOUNCEMENT, 'causer-id' => $item['author-id']], ['id' => $parent['id']]);
|
||||
Logger::info('Set announcement post-reason', ['uri-id' => $item['uri-id'], 'thr-parent-id' => $item['thr-parent-id'], 'uid' => $item['uid']]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1322,19 +1356,26 @@ class Item
|
|||
/**
|
||||
* Store a public item defined by their URI-ID for the given users
|
||||
*
|
||||
* @param integer $uri_id URI-ID of the given item
|
||||
* @param integer $uid The user that will receive the item entry
|
||||
* @param array $fields Additional fields to be stored
|
||||
* @param integer $uri_id URI-ID of the given item
|
||||
* @param integer $uid The user that will receive the item entry
|
||||
* @param array $fields Additional fields to be stored
|
||||
* @param integer $source_uid User id of the source post
|
||||
* @return integer stored item id
|
||||
*/
|
||||
public static function storeForUserByUriId(int $uri_id, int $uid, array $fields = [])
|
||||
public static function storeForUserByUriId(int $uri_id, int $uid, array $fields = [], int $source_uid = 0)
|
||||
{
|
||||
$item = Post::selectFirst(self::ITEM_FIELDLIST, ['uri-id' => $uri_id, 'uid' => 0]);
|
||||
if (!DBA::isResult($item)) {
|
||||
if ($uid == $source_uid) {
|
||||
Logger::warning('target UID must not be be equal to the source UID', ['uri-id' => $uri_id, 'uid' => $uid]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (($item['private'] == self::PRIVATE) || !in_array($item['network'], Protocol::FEDERATED)) {
|
||||
$item = Post::selectFirst(self::ITEM_FIELDLIST, ['uri-id' => $uri_id, 'uid' => $source_uid]);
|
||||
if (!DBA::isResult($item)) {
|
||||
Logger::warning('Item could not be fetched', ['uri-id' => $uri_id, 'uid' => $source_uid]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (($source_uid == 0) && (($item['private'] == self::PRIVATE) || !in_array($item['network'], Protocol::FEDERATED))) {
|
||||
Logger::notice('Item is private or not from a federated network. It will not be stored for the user.', ['uri-id' => $uri_id, 'uid' => $uid, 'private' => $item['private'], 'network' => $item['network']]);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1343,8 +1384,25 @@ class Item
|
|||
|
||||
$item = array_merge($item, $fields);
|
||||
|
||||
$is_reshare = ($item['gravity'] == GRAVITY_ACTIVITY) && ($item['verb'] == Activity::ANNOUNCE);
|
||||
|
||||
if ((($item['gravity'] == GRAVITY_PARENT) || $is_reshare) &&
|
||||
DI::pConfig()->get($uid, 'system', 'accept_only_sharer') &&
|
||||
!Contact::isSharingByURL($item['author-link'], $uid) &&
|
||||
!Contact::isSharingByURL($item['owner-link'], $uid)) {
|
||||
Logger::info('Contact is not a follower, thread will not be stored', ['author' => $item['author-link'], 'uid' => $uid]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((($item['gravity'] == GRAVITY_COMMENT) || $is_reshare) && !Post::exists(['uri-id' => $item['thr-parent-id'], 'uid' => $uid])) {
|
||||
// Only do an auto complete with the source uid "0" to prevent privavy problems
|
||||
$causer = $item['causer-id'] ?: $item['author-id'];
|
||||
$result = self::storeForUserByUriId($item['thr-parent-id'], $uid, ['causer-id' => $causer, 'post-reason' => self::PR_FETCHED]);
|
||||
Logger::info('Fetched thread parent', ['uri-id' => $item['thr-parent-id'], 'uid' => $uid, 'causer' => $causer, 'result' => $result]);
|
||||
}
|
||||
|
||||
$stored = self::storeForUser($item, $uid);
|
||||
Logger::info('Public item stored for user', ['uri-id' => $item['uri-id'], 'uid' => $uid, 'stored' => $stored]);
|
||||
Logger::info('Item stored for user', ['uri-id' => $item['uri-id'], 'uid' => $uid, 'source-uid' => $source_uid, 'stored' => $stored]);
|
||||
return $stored;
|
||||
}
|
||||
|
||||
|
|
@ -1364,11 +1422,18 @@ class Item
|
|||
}
|
||||
|
||||
unset($item['id']);
|
||||
unset($item['parent']);
|
||||
unset($item['mention']);
|
||||
unset($item['starred']);
|
||||
unset($item['unseen']);
|
||||
unset($item['psid']);
|
||||
unset($item['pinned']);
|
||||
unset($item['ignored']);
|
||||
unset($item['pubmail']);
|
||||
unset($item['forum_mode']);
|
||||
|
||||
unset($item['event-id']);
|
||||
unset($item['hidden']);
|
||||
unset($item['notification-type']);
|
||||
|
||||
$item['uid'] = $uid;
|
||||
$item['origin'] = 0;
|
||||
|
|
@ -1394,8 +1459,6 @@ class Item
|
|||
$item['contact-id'] = $self['id'];
|
||||
}
|
||||
|
||||
/// @todo Handling of "event-id"
|
||||
|
||||
$notify = false;
|
||||
if ($item['gravity'] == GRAVITY_PARENT) {
|
||||
$contact = DBA::selectFirst('contact', [], ['id' => $item['contact-id'], 'self' => false]);
|
||||
|
|
@ -1407,9 +1470,9 @@ class Item
|
|||
$distributed = self::insert($item, $notify, true);
|
||||
|
||||
if (!$distributed) {
|
||||
Logger::info("Distributed public item wasn't stored", ['uri-id' => $item['uri-id'], 'user' => $uid]);
|
||||
Logger::info("Distributed item wasn't stored", ['uri-id' => $item['uri-id'], 'user' => $uid]);
|
||||
} else {
|
||||
Logger::info('Distributed public item was stored', ['uri-id' => $item['uri-id'], 'user' => $uid, 'stored' => $distributed]);
|
||||
Logger::info('Distributed item was stored', ['uri-id' => $item['uri-id'], 'user' => $uid, 'stored' => $distributed]);
|
||||
}
|
||||
return $distributed;
|
||||
}
|
||||
|
|
@ -1850,6 +1913,15 @@ class Item
|
|||
return false;
|
||||
}
|
||||
|
||||
self::performActivity($item['id'], 'announce', $uid);
|
||||
|
||||
/**
|
||||
* All the following lines are only needed for private forums and compatibility to older systems without AP support.
|
||||
* A possible way would be that the followers list of a forum would always be readable by all followers.
|
||||
* So this would mean that the comment distribution could be done exactly for the intended audience.
|
||||
* Or possibly we could store the receivers that had been in the "announce" message above and use this.
|
||||
*/
|
||||
|
||||
// now change this copy of the post to a forum head message and deliver to all the tgroup members
|
||||
$self = DBA::selectFirst('contact', ['id', 'name', 'url', 'thumb'], ['uid' => $uid, 'self' => true]);
|
||||
if (!DBA::isResult($self)) {
|
||||
|
|
@ -1859,8 +1931,13 @@ class Item
|
|||
$owner_id = Contact::getIdForURL($self['url']);
|
||||
|
||||
// also reset all the privacy bits to the forum default permissions
|
||||
|
||||
$private = ($user['allow_cid'] || $user['allow_gid'] || $user['deny_cid'] || $user['deny_gid']) ? self::PRIVATE : self::PUBLIC;
|
||||
if ($user['allow_cid'] || $user['allow_gid'] || $user['deny_cid'] || $user['deny_gid']) {
|
||||
$private = self::PRIVATE;
|
||||
} elseif (DI::pConfig()->get($user['uid'], 'system', 'unlisted')) {
|
||||
$private = self::UNLISTED;
|
||||
} else {
|
||||
$private = self::PUBLIC;
|
||||
}
|
||||
|
||||
$psid = PermissionSet::getIdFromACL(
|
||||
$user['uid'],
|
||||
|
|
@ -1878,8 +1955,6 @@ class Item
|
|||
|
||||
Worker::add(['priority' => PRIORITY_HIGH, 'dont_fork' => true], 'Notifier', Delivery::POST, (int)$item['uri-id'], (int)$item['uid']);
|
||||
|
||||
self::performActivity($item['id'], 'announce', $uid);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -2667,6 +2742,23 @@ class Item
|
|||
}
|
||||
|
||||
$body = $item['body'] ?? '';
|
||||
$shared = BBCode::fetchShareAttributes($body);
|
||||
if (!empty($shared['guid'])) {
|
||||
$shared_item = Post::selectFirst(['uri-id', 'plink'], ['guid' => $shared['guid']]);
|
||||
$shared_uri_id = $shared_item['uri-id'] ?? 0;
|
||||
$shared_links = [strtolower($shared_item['plink'] ?? '')];
|
||||
$shared_attachments = Post\Media::splitAttachments($shared_uri_id, $shared['guid']);
|
||||
$shared_links = array_merge($shared_links, array_column($shared_attachments['visual'], 'url'));
|
||||
$shared_links = array_merge($shared_links, array_column($shared_attachments['link'], 'url'));
|
||||
$shared_links = array_merge($shared_links, array_column($shared_attachments['additional'], 'url'));
|
||||
$item['body'] = self::replaceVisualAttachments($shared_attachments, $item['body']);
|
||||
} else {
|
||||
$shared_uri_id = 0;
|
||||
$shared_links = [];
|
||||
}
|
||||
$attachments = Post\Media::splitAttachments($item['uri-id'], $item['guid'] ?? '', $shared_links);
|
||||
$item['body'] = self::replaceVisualAttachments($attachments, $item['body'] ?? '');
|
||||
|
||||
$item['body'] = preg_replace("/\s*\[attachment .*?\].*?\[\/attachment\]\s*/ism", "\n", $item['body']);
|
||||
self::putInCache($item);
|
||||
$item['body'] = $body;
|
||||
|
|
@ -2689,25 +2781,13 @@ class Item
|
|||
return $s;
|
||||
}
|
||||
|
||||
$shared = BBCode::fetchShareAttributes($item['body']);
|
||||
if (!empty($shared['guid'])) {
|
||||
$shared_item = Post::selectFirst(['uri-id', 'plink'], ['guid' => $shared['guid']]);
|
||||
$shared_uri_id = $shared_item['uri-id'] ?? 0;
|
||||
$shared_links = [strtolower($shared_item['plink'] ?? '')];
|
||||
$attachments = Post\Media::splitAttachments($shared_uri_id, $shared['guid']);
|
||||
$s = self::addVisualAttachments($attachments, $item, $s, true);
|
||||
$s = self::addLinkAttachment($attachments, $body, $s, true, []);
|
||||
$s = self::addNonVisualAttachments($attachments, $item, $s, true);
|
||||
$shared_links = array_merge($shared_links, array_column($attachments['visual'], 'url'));
|
||||
$shared_links = array_merge($shared_links, array_column($attachments['link'], 'url'));
|
||||
$shared_links = array_merge($shared_links, array_column($attachments['additional'], 'url'));
|
||||
if (!empty($shared_attachments)) {
|
||||
$s = self::addVisualAttachments($shared_attachments, $item, $s, true);
|
||||
$s = self::addLinkAttachment($shared_attachments, $body, $s, true, []);
|
||||
$s = self::addNonVisualAttachments($shared_attachments, $item, $s, true);
|
||||
$body = preg_replace("/\s*\[share .*?\].*?\[\/share\]\s*/ism", '', $body);
|
||||
} else {
|
||||
$shared_uri_id = 0;
|
||||
$shared_links = [];
|
||||
}
|
||||
|
||||
$attachments = Post\Media::splitAttachments($item['uri-id'], $item['guid'] ?? '', $shared_links);
|
||||
$s = self::addVisualAttachments($attachments, $item, $s, false);
|
||||
$s = self::addLinkAttachment($attachments, $body, $s, false, $shared_links);
|
||||
$s = self::addNonVisualAttachments($attachments, $item, $s, false);
|
||||
|
|
@ -2739,9 +2819,10 @@ class Item
|
|||
*
|
||||
* @param string $body
|
||||
* @param string $url
|
||||
* @param int $type
|
||||
* @return bool
|
||||
*/
|
||||
public static function containsLink(string $body, string $url)
|
||||
public static function containsLink(string $body, string $url, int $type = 0)
|
||||
{
|
||||
// Make sure that for example site parameters aren't used when testing if the link is contained in the body
|
||||
$urlparts = parse_url($url);
|
||||
|
|
@ -2749,6 +2830,12 @@ class Item
|
|||
unset($urlparts['fragment']);
|
||||
$url = Network::unparseURL($urlparts);
|
||||
|
||||
// Remove media links to only search in embedded content
|
||||
// @todo Check images for image link, audio for audio links, ...
|
||||
if (in_array($type, [Post\Media::AUDIO, Post\Media::VIDEO, Post\Media::IMAGE])) {
|
||||
$body = preg_replace("/\[url=[^\[\]]*\](.*)\[\/url\]/Usi", ' $1 ', $body);
|
||||
}
|
||||
|
||||
if (strpos($body, $url)) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -2761,6 +2848,28 @@ class Item
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace visual attachments in the body
|
||||
*
|
||||
* @param array $attachments
|
||||
* @param string $body
|
||||
* @return string modified body
|
||||
*/
|
||||
private static function replaceVisualAttachments(array $attachments, string $body)
|
||||
{
|
||||
$stamp1 = microtime(true);
|
||||
|
||||
foreach ($attachments['visual'] as $attachment) {
|
||||
if (!empty($attachment['preview'])) {
|
||||
$body = str_replace($attachment['preview'], Post\Media::getPreviewUrlForId($attachment['id'], Proxy::SIZE_LARGE), $body);
|
||||
} elseif ($attachment['filetype'] == 'image') {
|
||||
$body = str_replace($attachment['url'], Post\Media::getUrlForId($attachment['id']), $body);
|
||||
}
|
||||
}
|
||||
DI::profiler()->saveTimestamp($stamp1, 'rendering');
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add visual attachments to the content
|
||||
*
|
||||
|
|
@ -2777,16 +2886,12 @@ class Item
|
|||
|
||||
// @todo In the future we should make a single for the template engine with all media in it. This allows more flexibilty.
|
||||
foreach ($attachments['visual'] as $attachment) {
|
||||
if (self::containsLink($item['body'], $attachment['url'])) {
|
||||
if (self::containsLink($item['body'], $attachment['url'], $attachment['type'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$author = ['uid' => 0, 'id' => $item['author-id'],
|
||||
'network' => $item['author-network'], 'url' => $item['author-link']];
|
||||
$the_url = Contact::magicLinkByContact($author, $attachment['url']);
|
||||
|
||||
if (!empty($attachment['preview'])) {
|
||||
$preview_url = Proxy::proxifyUrl(Contact::magicLinkByContact($author, $attachment['preview']));
|
||||
$preview_url = Post\Media::getPreviewUrlForId($attachment['id'], Proxy::SIZE_LARGE);
|
||||
} else {
|
||||
$preview_url = '';
|
||||
}
|
||||
|
|
@ -2796,13 +2901,13 @@ class Item
|
|||
$media = Renderer::replaceMacros(Renderer::getMarkupTemplate('video_top.tpl'), [
|
||||
'$video' => [
|
||||
'id' => $attachment['id'],
|
||||
'src' => $the_url,
|
||||
'src' => $attachment['url'],
|
||||
'name' => $attachment['name'] ?: $attachment['url'],
|
||||
'preview' => $preview_url,
|
||||
'mime' => $attachment['mimetype'],
|
||||
],
|
||||
]);
|
||||
if ($item['post-type'] == Item::PT_VIDEO) {
|
||||
if (($item['post-type'] ?? null) == Item::PT_VIDEO) {
|
||||
$leading .= $media;
|
||||
} else {
|
||||
$trailing .= $media;
|
||||
|
|
@ -2811,25 +2916,22 @@ class Item
|
|||
$media = Renderer::replaceMacros(Renderer::getMarkupTemplate('content/audio.tpl'), [
|
||||
'$audio' => [
|
||||
'id' => $attachment['id'],
|
||||
'src' => $the_url,
|
||||
'name' => $attachment['name'] ?: $attachment['url'],
|
||||
'src' => $attachment['url'],
|
||||
'name' => $attachment['name'] ?: $attachment['url'],
|
||||
'mime' => $attachment['mimetype'],
|
||||
],
|
||||
]);
|
||||
if ($item['post-type'] == Item::PT_AUDIO) {
|
||||
if (($item['post-type'] ?? null) == Item::PT_AUDIO) {
|
||||
$leading .= $media;
|
||||
} else {
|
||||
$trailing .= $media;
|
||||
}
|
||||
} elseif ($attachment['filetype'] == 'image') {
|
||||
if (empty($preview_url) && (max($attachment['width'], $attachment['height']) > 600)) {
|
||||
$preview_url = Proxy::proxifyUrl($the_url, false, ($attachment['width'] > $attachment['height']) ? Proxy::SIZE_MEDIUM : Proxy::SIZE_LARGE);
|
||||
}
|
||||
$media = Renderer::replaceMacros(Renderer::getMarkupTemplate('content/image.tpl'), [
|
||||
'$image' => [
|
||||
'src' => Proxy::proxifyUrl($the_url),
|
||||
'preview' => $preview_url,
|
||||
'attachment' => $attachment,
|
||||
'src' => Post\Media::getUrlForId($attachment['id']),
|
||||
'preview' => Post\Media::getPreviewUrlForId($attachment['id'], ($attachment['width'] > $attachment['height']) ? Proxy::SIZE_MEDIUM : Proxy::SIZE_LARGE),
|
||||
'attachment' => $attachment,
|
||||
],
|
||||
]);
|
||||
// On Diaspora posts the attached pictures are leading
|
||||
|
|
@ -2907,11 +3009,11 @@ class Item
|
|||
'type' => 'link',
|
||||
'url' => $attachment['url']];
|
||||
|
||||
if ($preview) {
|
||||
if ($preview && !empty($attachment['preview'])) {
|
||||
if ($attachment['preview-width'] >= 500) {
|
||||
$data['image'] = $attachment['preview'] ?? '';
|
||||
$data['image'] = Post\Media::getPreviewUrlForId($attachment['id'], Proxy::SIZE_MEDIUM);
|
||||
} else {
|
||||
$data['preview'] = $attachment['preview'] ?? '';
|
||||
$data['preview'] = Post\Media::getPreviewUrlForId($attachment['id'], Proxy::SIZE_MEDIUM);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2955,7 +3057,7 @@ class Item
|
|||
|
||||
// @todo Use a template
|
||||
$rendered = BBCode::convertAttachment('', BBCode::INTERNAL, false, $data);
|
||||
} elseif (!self::containsLink($content, $data['url'])) {
|
||||
} elseif (!self::containsLink($content, $data['url'], Post\Media::HTML)) {
|
||||
$rendered = Renderer::replaceMacros(Renderer::getMarkupTemplate('content/link.tpl'), [
|
||||
'$url' => $data['url'],
|
||||
'$title' => $data['title'],
|
||||
|
|
@ -3288,18 +3390,18 @@ class Item
|
|||
{
|
||||
$shared = BBCode::fetchShareAttributes($item['body']);
|
||||
if (empty($shared['link'])) {
|
||||
return $item['body'];
|
||||
return $item['body'];
|
||||
}
|
||||
|
||||
|
||||
$id = self::fetchByLink($shared['link']);
|
||||
Logger::info('Fetched shared post', ['uri-id' => $item['uri-id'], 'id' => $id, 'author' => $shared['profile'], 'url' => $shared['link'], 'guid' => $shared['guid'], 'callstack' => System::callstack()]);
|
||||
if (!$id) {
|
||||
return $item['body'];
|
||||
return $item['body'];
|
||||
}
|
||||
|
||||
$shared_item = Post::selectFirst(['author-name', 'author-link', 'author-avatar', 'plink', 'created', 'guid', 'title', 'body'], ['id' => $id]);
|
||||
if (!DBA::isResult($shared_item)) {
|
||||
return $item['body'];
|
||||
return $item['body'];
|
||||
}
|
||||
|
||||
$shared_content = BBCode::getShareOpeningTag($shared_item['author-name'], $shared_item['author-link'], $shared_item['author-avatar'], $shared_item['plink'], $shared_item['created'], $shared_item['guid']);
|
||||
|
|
|
|||
|
|
@ -36,15 +36,14 @@ use Friendica\Worker\Delivery;
|
|||
class Mail
|
||||
{
|
||||
/**
|
||||
* Insert received private message
|
||||
* Insert private message
|
||||
*
|
||||
* @param array $msg
|
||||
* @param bool $notifiction
|
||||
* @return int|boolean Message ID or false on error
|
||||
*/
|
||||
public static function insert($msg)
|
||||
public static function insert($msg, $notifiction = true)
|
||||
{
|
||||
$user = User::getById($msg['uid']);
|
||||
|
||||
if (!isset($msg['reply'])) {
|
||||
$msg['reply'] = DBA::exists('mail', ['parent-uri' => $msg['parent-uri']]);
|
||||
}
|
||||
|
|
@ -63,6 +62,10 @@ class Mail
|
|||
|
||||
$msg['created'] = (!empty($msg['created']) ? DateTimeFormat::utc($msg['created']) : DateTimeFormat::utcNow());
|
||||
|
||||
$msg['author-id'] = Contact::getIdForURL($msg['from-url'], 0, false);
|
||||
$msg['uri-id'] = ItemURI::insert(['uri' => $msg['uri'], 'guid' => $msg['guid']]);
|
||||
$msg['parent-uri-id'] = ItemURI::getIdByURI($msg['parent-uri']);
|
||||
|
||||
DBA::lock('mail');
|
||||
|
||||
if (DBA::exists('mail', ['uri' => $msg['uri'], 'uid' => $msg['uid']])) {
|
||||
|
|
@ -71,6 +74,16 @@ class Mail
|
|||
return false;
|
||||
}
|
||||
|
||||
if ($msg['reply']) {
|
||||
$reply = DBA::selectFirst('mail', ['uri', 'uri-id'], ['parent-uri' => $msg['parent-uri'], 'reply' => false]);
|
||||
|
||||
$msg['thr-parent'] = $reply['uri'];
|
||||
$msg['thr-parent-id'] = $reply['uri-id'];
|
||||
} else {
|
||||
$msg['thr-parent'] = $msg['uri'];
|
||||
$msg['thr-parent-id'] = $msg['uri-id'];
|
||||
}
|
||||
|
||||
DBA::insert('mail', $msg);
|
||||
|
||||
$msg['id'] = DBA::lastInsertId();
|
||||
|
|
@ -81,19 +94,22 @@ class Mail
|
|||
DBA::update('conv', ['updated' => DateTimeFormat::utcNow()], ['id' => $msg['convid']]);
|
||||
}
|
||||
|
||||
// send notifications.
|
||||
$notif_params = [
|
||||
'type' => Notification\Type::MAIL,
|
||||
'otype' => Notification\ObjectType::MAIL,
|
||||
'verb' => Activity::POST,
|
||||
'uid' => $user['uid'],
|
||||
'cid' => $msg['contact-id'],
|
||||
'link' => DI::baseUrl() . '/message/' . $msg['id'],
|
||||
];
|
||||
if ($notifiction) {
|
||||
$user = User::getById($msg['uid']);
|
||||
// send notifications.
|
||||
$notif_params = [
|
||||
'type' => Notification\Type::MAIL,
|
||||
'otype' => Notification\ObjectType::MAIL,
|
||||
'verb' => Activity::POST,
|
||||
'uid' => $user['uid'],
|
||||
'cid' => $msg['contact-id'],
|
||||
'link' => DI::baseUrl() . '/message/' . $msg['id'],
|
||||
];
|
||||
|
||||
notification($notif_params);
|
||||
notification($notif_params);
|
||||
|
||||
Logger::info('Mail is processed, notification was sent.', ['id' => $msg['id'], 'uri' => $msg['uri']]);
|
||||
Logger::info('Mail is processed, notification was sent.', ['id' => $msg['id'], 'uri' => $msg['uri']]);
|
||||
}
|
||||
|
||||
return $msg['id'];
|
||||
}
|
||||
|
|
@ -181,9 +197,7 @@ class Mail
|
|||
$replyto = $convuri;
|
||||
}
|
||||
|
||||
$post_id = null;
|
||||
$success = DBA::insert(
|
||||
'mail',
|
||||
$post_id = self::insert(
|
||||
[
|
||||
'uid' => local_user(),
|
||||
'guid' => $guid,
|
||||
|
|
@ -200,13 +214,9 @@ class Mail
|
|||
'uri' => $uri,
|
||||
'parent-uri' => $replyto,
|
||||
'created' => DateTimeFormat::utcNow()
|
||||
]
|
||||
], false
|
||||
);
|
||||
|
||||
if ($success) {
|
||||
$post_id = DBA::lastInsertId();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* When a photo was uploaded into the message using the (profile wall) ajax
|
||||
|
|
@ -287,8 +297,7 @@ class Mail
|
|||
return -4;
|
||||
}
|
||||
|
||||
DBA::insert(
|
||||
'mail',
|
||||
self::insert(
|
||||
[
|
||||
'uid' => $recipient['uid'],
|
||||
'guid' => $guid,
|
||||
|
|
@ -306,7 +315,7 @@ class Mail
|
|||
'parent-uri' => $me['url'],
|
||||
'created' => DateTimeFormat::utcNow(),
|
||||
'unknown' => 1
|
||||
]
|
||||
], false
|
||||
);
|
||||
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ use Friendica\Core\System;
|
|||
use Friendica\Database\DBA;
|
||||
use Friendica\Database\DBStructure;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Storage\ExternalResource;
|
||||
use Friendica\Model\Storage\SystemResource;
|
||||
use Friendica\Object\Image;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
|
@ -244,13 +245,17 @@ class Photo
|
|||
* Construct a photo array for a system resource image
|
||||
*
|
||||
* @param string $filename Image file name relative to code root
|
||||
* @param string $mimetype Image mime type. Defaults to "image/jpeg"
|
||||
* @param string $mimetype Image mime type. Is guessed by file name when empty.
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function createPhotoForSystemResource($filename, $mimetype = "image/jpeg")
|
||||
public static function createPhotoForSystemResource($filename, $mimetype = '')
|
||||
{
|
||||
if (empty($mimetype)) {
|
||||
$mimetype = Images::guessTypeByExtension($filename);
|
||||
}
|
||||
|
||||
$fields = self::getFields();
|
||||
$values = array_fill(0, count($fields), "");
|
||||
|
||||
|
|
@ -263,6 +268,33 @@ class Photo
|
|||
return $photo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a photo array for an external resource image
|
||||
*
|
||||
* @param string $url Image URL
|
||||
* @param int $uid User ID of the requesting person
|
||||
* @param string $mimetype Image mime type. Is guessed by file name when empty.
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function createPhotoForExternalResource($url, $uid = 0, $mimetype = '')
|
||||
{
|
||||
if (empty($mimetype)) {
|
||||
$mimetype = Images::guessTypeByExtension($url);
|
||||
}
|
||||
|
||||
$fields = self::getFields();
|
||||
$values = array_fill(0, count($fields), "");
|
||||
|
||||
$photo = array_combine($fields, $values);
|
||||
$photo['backend-class'] = ExternalResource::NAME;
|
||||
$photo['backend-ref'] = json_encode(['url' => $url, 'uid' => $uid]);
|
||||
$photo['type'] = $mimetype;
|
||||
$photo['cacheable'] = true;
|
||||
|
||||
return $photo;
|
||||
}
|
||||
|
||||
/**
|
||||
* store photo metadata in db and binary in default backend
|
||||
|
|
|
|||
|
|
@ -124,24 +124,22 @@ class Post
|
|||
}
|
||||
|
||||
/**
|
||||
* Check if post data exists
|
||||
* Check if post-user-view records exists
|
||||
*
|
||||
* @param array $condition array of fields for condition
|
||||
* @param bool $user_mode true = post-user-view, false = post-view
|
||||
*
|
||||
* @return boolean Are there rows for that condition?
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function exists($condition, bool $user_mode = true) {
|
||||
return DBA::exists($user_mode ? 'post-user-view' : 'post-view', $condition);
|
||||
public static function exists($condition) {
|
||||
return DBA::exists('post-user-view', $condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the posts satisfying the provided condition
|
||||
* Counts the post-user-view records satisfying the provided condition
|
||||
*
|
||||
* @param array $condition array of fields for condition
|
||||
* @param array $params Array of several parameters
|
||||
* @param bool $user_mode true = post-user-view, false = post-view
|
||||
*
|
||||
* @return int
|
||||
*
|
||||
|
|
@ -153,17 +151,39 @@ class Post
|
|||
* $count = Post::count($condition);
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function count(array $condition = [], array $params = [], bool $user_mode = true)
|
||||
public static function count(array $condition = [], array $params = [])
|
||||
{
|
||||
return DBA::count($user_mode ? 'post-user-view' : 'post-view', $condition, $params);
|
||||
return DBA::count('post-user-view', $condition, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a single record from the post table and returns it in an associative array
|
||||
* Counts the post-view records satisfying the provided condition
|
||||
*
|
||||
* @param array $condition array of fields for condition
|
||||
* @param array $params Array of several parameters
|
||||
*
|
||||
* @return int
|
||||
*
|
||||
* Example:
|
||||
* $condition = ["network" => 'dspr'];
|
||||
* or:
|
||||
* $condition = ["`network` IN (?, ?)", 1, 'dfrn', 'dspr'];
|
||||
*
|
||||
* $count = Post::count($condition);
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function countPosts(array $condition = [], array $params = [])
|
||||
{
|
||||
return DBA::count('post-view', $condition, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a single record from the post-user-view view and returns it in an associative array
|
||||
*
|
||||
* @param array $fields
|
||||
* @param array $condition
|
||||
* @param array $params
|
||||
* @param bool $user_mode true = post-user-view, false = post-view
|
||||
* @return bool|array
|
||||
* @throws \Exception
|
||||
* @see DBA::select
|
||||
|
|
@ -184,7 +204,32 @@ class Post
|
|||
}
|
||||
|
||||
/**
|
||||
* Retrieve a single record from the post-thread table and returns it in an associative array
|
||||
* Retrieve a single record from the post-view view and returns it in an associative array
|
||||
*
|
||||
* @param array $fields
|
||||
* @param array $condition
|
||||
* @param array $params
|
||||
* @return bool|array
|
||||
* @throws \Exception
|
||||
* @see DBA::select
|
||||
*/
|
||||
public static function selectFirstPost(array $fields = [], array $condition = [], $params = [])
|
||||
{
|
||||
$params['limit'] = 1;
|
||||
|
||||
$result = self::selectPosts($fields, $condition, $params);
|
||||
|
||||
if (is_bool($result)) {
|
||||
return $result;
|
||||
} else {
|
||||
$row = self::fetch($result);
|
||||
DBA::close($result);
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a single record from the post-thread-user-view view and returns it in an associative array
|
||||
*
|
||||
* @param array $fields
|
||||
* @param array $condition
|
||||
|
|
@ -209,7 +254,7 @@ class Post
|
|||
}
|
||||
|
||||
/**
|
||||
* Select rows from the post table and returns them as an array
|
||||
* Select rows from the post-user-view view and returns them as an array
|
||||
*
|
||||
* @param array $selected Array of selected fields, empty for all
|
||||
* @param array $condition Array of fields for condition
|
||||
|
|
@ -262,23 +307,37 @@ class Post
|
|||
}
|
||||
|
||||
/**
|
||||
* Select rows from the post table
|
||||
* Select rows from the post-user-view view
|
||||
*
|
||||
* @param array $selected Array of selected fields, empty for all
|
||||
* @param array $condition Array of fields for condition
|
||||
* @param array $params Array of several parameters
|
||||
* @param bool $user_mode true = post-user-view, false = post-view
|
||||
*
|
||||
* @return boolean|object
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function select(array $selected = [], array $condition = [], $params = [], bool $user_mode = true)
|
||||
public static function select(array $selected = [], array $condition = [], $params = [])
|
||||
{
|
||||
return self::selectView($user_mode ? 'post-user-view' : 'post-view', $selected, $condition, $params);
|
||||
return self::selectView('post-user-view', $selected, $condition, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select rows from the post table
|
||||
* Select rows from the post-view view
|
||||
*
|
||||
* @param array $selected Array of selected fields, empty for all
|
||||
* @param array $condition Array of fields for condition
|
||||
* @param array $params Array of several parameters
|
||||
*
|
||||
* @return boolean|object
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function selectPosts(array $selected = [], array $condition = [], $params = [])
|
||||
{
|
||||
return self::selectView('post-view', $selected, $condition, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select rows from the post-thread-user-view view
|
||||
*
|
||||
* @param array $selected Array of selected fields, empty for all
|
||||
* @param array $condition Array of fields for condition
|
||||
|
|
@ -335,7 +394,7 @@ class Post
|
|||
}
|
||||
|
||||
/**
|
||||
* Select rows from the post view for a given user
|
||||
* Select rows from the post-user-view view for a given user
|
||||
*
|
||||
* @param integer $uid User ID
|
||||
* @param array $selected Array of selected fields, empty for all
|
||||
|
|
@ -350,8 +409,24 @@ class Post
|
|||
return self::selectViewForUser('post-user-view', $uid, $selected, $condition, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select rows from the post view for a given user
|
||||
/**
|
||||
* Select rows from the post-view view for a given user
|
||||
*
|
||||
* @param integer $uid User ID
|
||||
* @param array $selected Array of selected fields, empty for all
|
||||
* @param array $condition Array of fields for condition
|
||||
* @param array $params Array of several parameters
|
||||
*
|
||||
* @return boolean|object
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function selectPostsForUser($uid, array $selected = [], array $condition = [], $params = [])
|
||||
{
|
||||
return self::selectViewForUser('post-view', $uid, $selected, $condition, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select rows from the post-thread-user-view view for a given user
|
||||
*
|
||||
* @param integer $uid User ID
|
||||
* @param array $selected Array of selected fields, empty for all
|
||||
|
|
@ -367,7 +442,7 @@ class Post
|
|||
}
|
||||
|
||||
/**
|
||||
* Retrieve a single record from the post view for a given user and returns it in an associative array
|
||||
* Retrieve a single record from the post-user-view view for a given user and returns it in an associative array
|
||||
*
|
||||
* @param integer $uid User ID
|
||||
* @param array $selected
|
||||
|
|
@ -393,7 +468,7 @@ class Post
|
|||
}
|
||||
|
||||
/**
|
||||
* Select pinned rows from the item table for a given user
|
||||
* Select pinned rows from the post-thread-user table for a given user
|
||||
*
|
||||
* @param integer $uid User ID
|
||||
* @param array $selected Array of selected fields, empty for all
|
||||
|
|
|
|||
|
|
@ -28,9 +28,12 @@ use Friendica\Database\Database;
|
|||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Photo;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Util\Images;
|
||||
use Friendica\Util\Network;
|
||||
use Friendica\Util\ParseUrl;
|
||||
use Friendica\Util\Proxy;
|
||||
use Friendica\Util\Strings;
|
||||
|
||||
/**
|
||||
|
|
@ -157,6 +160,10 @@ class Media
|
|||
*/
|
||||
public static function fetchAdditionalData(array $media)
|
||||
{
|
||||
if (Network::isLocalLink($media['url'])) {
|
||||
$media = self::fetchLocalData($media);
|
||||
}
|
||||
|
||||
// Fetch the mimetype or size if missing.
|
||||
if (empty($media['mimetype']) || empty($media['size'])) {
|
||||
$timeout = DI::config()->get('system', 'xrd_timeout');
|
||||
|
|
@ -215,6 +222,36 @@ class Media
|
|||
return $media;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch media data from local resources
|
||||
* @param array $media
|
||||
* @return array media with added data
|
||||
*/
|
||||
private static function fetchLocalData(array $media)
|
||||
{
|
||||
if (!preg_match('|.*?/photo/(.*[a-fA-F0-9])\-(.*[0-9])\..*[\w]|', $media['url'] ?? '', $matches)) {
|
||||
return $media;
|
||||
}
|
||||
$photo = Photo::selectFirst([], ['resource-id' => $matches[1], 'scale' => $matches[2]]);
|
||||
if (!empty($photo)) {
|
||||
$media['mimetype'] = $photo['type'];
|
||||
$media['size'] = $photo['datasize'];
|
||||
$media['width'] = $photo['width'];
|
||||
$media['height'] = $photo['height'];
|
||||
}
|
||||
|
||||
if (!preg_match('|.*?/photo/(.*[a-fA-F0-9])\-(.*[0-9])\..*[\w]|', $media['preview'] ?? '', $matches)) {
|
||||
return $media;
|
||||
}
|
||||
$photo = Photo::selectFirst([], ['resource-id' => $matches[1], 'scale' => $matches[2]]);
|
||||
if (!empty($photo)) {
|
||||
$media['preview-width'] = $photo['width'];
|
||||
$media['preview-height'] = $photo['height'];
|
||||
}
|
||||
|
||||
return $media;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the detected type to the media array
|
||||
*
|
||||
|
|
@ -351,7 +388,7 @@ class Media
|
|||
|
||||
foreach ($attachments as $attachment) {
|
||||
// Only store attachments that are part of the unshared body
|
||||
if (strpos($unshared_body, $attachment['url']) !== false) {
|
||||
if (Item::containsLink($unshared_body, $attachment['url'], $attachment['type'])) {
|
||||
self::insert($attachment);
|
||||
}
|
||||
}
|
||||
|
|
@ -494,10 +531,10 @@ class Media
|
|||
|
||||
/**
|
||||
* Split the attachment media in the three segments "visual", "link" and "additional"
|
||||
*
|
||||
* @param int $uri_id
|
||||
*
|
||||
* @param int $uri_id
|
||||
* @param string $guid
|
||||
* @param array $links ist of links that shouldn't be added
|
||||
* @param array $links ist of links that shouldn't be added
|
||||
* @return array attachments
|
||||
*/
|
||||
public static function splitAttachments(int $uri_id, string $guid = '', array $links = [])
|
||||
|
|
@ -526,7 +563,7 @@ class Media
|
|||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!empty($medium['preview'])) {
|
||||
$previews[] = $medium['preview'];
|
||||
}
|
||||
|
|
@ -600,7 +637,7 @@ class Media
|
|||
$body = preg_replace("/\s*\[attachment .*?\].*?\[\/attachment\]\s*/ism", '', $body);
|
||||
|
||||
foreach (self::getByURIId($uriid, [self::IMAGE, self::AUDIO, self::VIDEO]) as $media) {
|
||||
if (Item::containsLink($body, $media['url'])) {
|
||||
if (Item::containsLink($body, $media['url'], $media['type'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -631,4 +668,64 @@ class Media
|
|||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get preview link for given media id
|
||||
*
|
||||
* @param integer $id media id
|
||||
* @param string $size One of the ProxyUtils::SIZE_* constants
|
||||
* @return string preview link
|
||||
*/
|
||||
public static function getPreviewUrlForId(int $id, string $size = ''):string
|
||||
{
|
||||
$url = DI::baseUrl() . '/photo/preview/';
|
||||
switch ($size) {
|
||||
case Proxy::SIZE_MICRO:
|
||||
$url .= Proxy::PIXEL_MICRO . '/';
|
||||
break;
|
||||
case Proxy::SIZE_THUMB:
|
||||
$url .= Proxy::PIXEL_THUMB . '/';
|
||||
break;
|
||||
case Proxy::SIZE_SMALL:
|
||||
$url .= Proxy::PIXEL_SMALL . '/';
|
||||
break;
|
||||
case Proxy::SIZE_MEDIUM:
|
||||
$url .= Proxy::PIXEL_MEDIUM . '/';
|
||||
break;
|
||||
case Proxy::SIZE_LARGE:
|
||||
$url .= Proxy::PIXEL_LARGE . '/';
|
||||
break;
|
||||
}
|
||||
return $url . $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get media link for given media id
|
||||
*
|
||||
* @param integer $id media id
|
||||
* @param string $size One of the ProxyUtils::SIZE_* constants
|
||||
* @return string media link
|
||||
*/
|
||||
public static function getUrlForId(int $id, string $size = ''):string
|
||||
{
|
||||
$url = DI::baseUrl() . '/photo/media/';
|
||||
switch ($size) {
|
||||
case Proxy::SIZE_MICRO:
|
||||
$url .= Proxy::PIXEL_MICRO . '/';
|
||||
break;
|
||||
case Proxy::SIZE_THUMB:
|
||||
$url .= Proxy::PIXEL_THUMB . '/';
|
||||
break;
|
||||
case Proxy::SIZE_SMALL:
|
||||
$url .= Proxy::PIXEL_SMALL . '/';
|
||||
break;
|
||||
case Proxy::SIZE_MEDIUM:
|
||||
$url .= Proxy::PIXEL_MEDIUM . '/';
|
||||
break;
|
||||
case Proxy::SIZE_LARGE:
|
||||
$url .= Proxy::PIXEL_LARGE . '/';
|
||||
break;
|
||||
}
|
||||
return $url . $id;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ use Friendica\Model\Post;
|
|||
use Friendica\Util\Strings;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Protocol\Activity;
|
||||
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
||||
class UserNotification
|
||||
{
|
||||
|
|
@ -128,8 +128,8 @@ class UserNotification
|
|||
*/
|
||||
public static function setNotification(int $uri_id, int $uid)
|
||||
{
|
||||
$fields = ['id', 'uri-id', 'parent-uri-id', 'uid', 'body', 'parent', 'gravity',
|
||||
'private', 'contact-id', 'thr-parent', 'parent-uri-id', 'parent-uri', 'author-id', 'verb'];
|
||||
$fields = ['id', 'uri-id', 'parent-uri-id', 'uid', 'body', 'parent', 'gravity', 'vid', 'gravity',
|
||||
'private', 'contact-id', 'thr-parent', 'thr-parent-id', 'parent-uri-id', 'parent-uri', 'author-id', 'verb'];
|
||||
$item = Post::selectFirst($fields, ['uri-id' => $uri_id, 'uid' => $uid, 'origin' => false]);
|
||||
if (!DBA::isResult($item)) {
|
||||
return;
|
||||
|
|
@ -148,7 +148,7 @@ class UserNotification
|
|||
}
|
||||
|
||||
// Add every user who participated so far in this thread
|
||||
// This can only happen with participations on global items. (means: uid = 0)
|
||||
// This can only happen with participations on global items. (means: uid = 0)
|
||||
$users = DBA::p("SELECT DISTINCT(`contact-uid`) AS `uid` FROM `post-user-view`
|
||||
WHERE `contact-uid` != 0 AND `parent-uri-id` = ? AND `uid` = ?", $item['parent-uri-id'], $uid);
|
||||
while ($user = DBA::fetch($users)) {
|
||||
|
|
@ -177,6 +177,10 @@ class UserNotification
|
|||
|
||||
if (self::checkShared($item, $uid)) {
|
||||
$notification_type = $notification_type | self::NOTIF_SHARED;
|
||||
self::insertNoticationByItem(self::NOTIF_SHARED, $uid, $item);
|
||||
$notified = true;
|
||||
} else {
|
||||
$notified = false;
|
||||
}
|
||||
|
||||
$profiles = self::getProfileForUser($uid);
|
||||
|
|
@ -194,38 +198,64 @@ class UserNotification
|
|||
return;
|
||||
}
|
||||
|
||||
// Only create notifications for posts and comments, not for activities
|
||||
if (in_array($item['gravity'], [GRAVITY_PARENT, GRAVITY_COMMENT])) {
|
||||
if (self::checkImplicitMention($item, $profiles)) {
|
||||
$notification_type = $notification_type | self::NOTIF_IMPLICIT_TAGGED;
|
||||
}
|
||||
|
||||
if (self::checkExplicitMention($item, $profiles)) {
|
||||
$notification_type = $notification_type | self::NOTIF_EXPLICIT_TAGGED;
|
||||
}
|
||||
|
||||
if (self::checkCommentedThread($item, $contacts)) {
|
||||
$notification_type = $notification_type | self::NOTIF_THREAD_COMMENT;
|
||||
}
|
||||
|
||||
if (self::checkDirectComment($item, $contacts)) {
|
||||
$notification_type = $notification_type | self::NOTIF_DIRECT_COMMENT;
|
||||
}
|
||||
|
||||
if (self::checkDirectCommentedThread($item, $contacts)) {
|
||||
$notification_type = $notification_type | self::NOTIF_DIRECT_THREAD_COMMENT;
|
||||
}
|
||||
|
||||
if (self::checkCommentedParticipation($item, $contacts)) {
|
||||
$notification_type = $notification_type | self::NOTIF_COMMENT_PARTICIPATION;
|
||||
}
|
||||
|
||||
if (self::checkActivityParticipation($item, $contacts)) {
|
||||
$notification_type = $notification_type | self::NOTIF_ACTIVITY_PARTICIPATION;
|
||||
if (self::checkExplicitMention($item, $profiles)) {
|
||||
$notification_type = $notification_type | self::NOTIF_EXPLICIT_TAGGED;
|
||||
if (!$notified) {
|
||||
self::insertNoticationByItem( self::NOTIF_EXPLICIT_TAGGED, $uid, $item);
|
||||
$notified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($notification_type)) {
|
||||
if (self::checkImplicitMention($item, $profiles)) {
|
||||
$notification_type = $notification_type | self::NOTIF_IMPLICIT_TAGGED;
|
||||
if (!$notified) {
|
||||
self::insertNoticationByItem(self::NOTIF_IMPLICIT_TAGGED, $uid, $item);
|
||||
$notified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (self::checkDirectComment($item, $contacts)) {
|
||||
$notification_type = $notification_type | self::NOTIF_DIRECT_COMMENT;
|
||||
if (!$notified) {
|
||||
self::insertNoticationByItem(self::NOTIF_DIRECT_COMMENT, $uid, $item);
|
||||
$notified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (self::checkDirectCommentedThread($item, $contacts)) {
|
||||
$notification_type = $notification_type | self::NOTIF_DIRECT_THREAD_COMMENT;
|
||||
if (!$notified) {
|
||||
self::insertNoticationByItem(self::NOTIF_DIRECT_THREAD_COMMENT, $uid, $item);
|
||||
$notified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (self::checkCommentedThread($item, $contacts)) {
|
||||
$notification_type = $notification_type | self::NOTIF_THREAD_COMMENT;
|
||||
if (!$notified) {
|
||||
self::insertNoticationByItem(self::NOTIF_THREAD_COMMENT, $uid, $item);
|
||||
$notified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (self::checkCommentedParticipation($item, $contacts)) {
|
||||
$notification_type = $notification_type | self::NOTIF_COMMENT_PARTICIPATION;
|
||||
if (!$notified) {
|
||||
self::insertNoticationByItem(self::NOTIF_COMMENT_PARTICIPATION, $uid, $item);
|
||||
$notified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (self::checkActivityParticipation($item, $contacts)) {
|
||||
$notification_type = $notification_type | self::NOTIF_ACTIVITY_PARTICIPATION;
|
||||
if (!$notified) {
|
||||
self::insertNoticationByItem(self::NOTIF_ACTIVITY_PARTICIPATION, $uid, $item);
|
||||
$notified = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Only create notifications for posts and comments, not for activities
|
||||
if (empty($notification_type) || !in_array($item['gravity'], [GRAVITY_PARENT, GRAVITY_COMMENT])) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -236,6 +266,61 @@ class UserNotification
|
|||
self::update($item['uri-id'], $uid, $fields, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a notification entry for a given item array
|
||||
*
|
||||
* @param int $type User notification type
|
||||
* @param int $uid User ID
|
||||
* @param array $item Item array
|
||||
* @return boolean
|
||||
*/
|
||||
private static function insertNoticationByItem(int $type, int $uid, array $item)
|
||||
{
|
||||
if (($item['gravity'] == GRAVITY_ACTIVITY) &&
|
||||
!in_array($type, [self::NOTIF_DIRECT_COMMENT, self::NOTIF_DIRECT_THREAD_COMMENT])) {
|
||||
// Activities are only stored when performed on the user's post or comment
|
||||
return;
|
||||
}
|
||||
|
||||
$fields = [
|
||||
'uid' => $uid,
|
||||
'vid' => $item['vid'],
|
||||
'type' => $type,
|
||||
'actor-id' => $item['author-id'],
|
||||
'parent-uri-id' => $item['parent-uri-id'],
|
||||
'created' => DateTimeFormat::utcNow(),
|
||||
];
|
||||
|
||||
if ($item['gravity'] == GRAVITY_ACTIVITY) {
|
||||
$fields['target-uri-id'] = $item['thr-parent-id'];
|
||||
} else {
|
||||
$fields['target-uri-id'] = $item['uri-id'];
|
||||
}
|
||||
|
||||
return DBA::insert('notification', $fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a notification entry
|
||||
*
|
||||
* @param int $actor Contact ID of the actor
|
||||
* @param int $vid Verb ID
|
||||
* @param int $uid User ID
|
||||
* @return boolean
|
||||
*/
|
||||
public static function insertNotication(int $actor, int $vid, int $uid)
|
||||
{
|
||||
$fields = [
|
||||
'uid' => $uid,
|
||||
'vid' => $vid,
|
||||
'type' => self::NOTIF_NONE,
|
||||
'actor-id' => $actor,
|
||||
'created' => DateTimeFormat::utcNow(),
|
||||
];
|
||||
|
||||
return DBA::insert('notification', $fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all profiles (contact URL) of a given user
|
||||
* @param int $uid User ID
|
||||
|
|
|
|||
|
|
@ -29,13 +29,16 @@ use Friendica\Core\Hook;
|
|||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\Core\Search;
|
||||
use Friendica\Core\Session;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Core\Worker;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Protocol\Activity;
|
||||
use Friendica\Protocol\Diaspora;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\HTTPSignature;
|
||||
use Friendica\Util\Network;
|
||||
use Friendica\Util\Proxy as ProxyUtils;
|
||||
use Friendica\Util\Strings;
|
||||
|
|
@ -84,6 +87,71 @@ class Profile
|
|||
return DBA::selectToArray('profile', $fields, ['uid' => $uid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a profile entry and distribute the changes if needed
|
||||
*
|
||||
* @param array $fields
|
||||
* @param integer $uid
|
||||
* @return boolean
|
||||
*/
|
||||
public static function update(array $fields, int $uid): bool
|
||||
{
|
||||
$old_owner = User::getOwnerDataById($uid);
|
||||
if (empty($old_owner)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!DBA::update('profile', $fields, ['uid' => $uid])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$update = Contact::updateSelfFromUserID($uid);
|
||||
|
||||
$owner = User::getOwnerDataById($uid);
|
||||
if (empty($owner)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($old_owner['name'] != $owner['name']) {
|
||||
User::update(['username' => $owner['name']], $uid);
|
||||
}
|
||||
|
||||
$profile_fields = ['postal-code', 'dob', 'prv_keywords', 'homepage'];
|
||||
foreach ($profile_fields as $field) {
|
||||
if ($old_owner[$field] != $owner[$field]) {
|
||||
$update = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($update) {
|
||||
self::publishUpdate($uid, ($old_owner['net-publish'] != $owner['net-publish']));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish a changed profile
|
||||
* @param int $uid
|
||||
* @param bool $force Force publishing to the directory
|
||||
*/
|
||||
public static function publishUpdate(int $uid, bool $force = false)
|
||||
{
|
||||
$owner = User::getOwnerDataById($uid);
|
||||
if (empty($owner)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($owner['net-publish'] || $force) {
|
||||
// Update global directory in background
|
||||
if (Search::getGlobalDirectory()) {
|
||||
Worker::add(PRIORITY_LOW, 'Directory', $owner['url']);
|
||||
}
|
||||
}
|
||||
|
||||
Worker::add(PRIORITY_LOW, 'ProfileUpdate', $uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a formatted location string from the given profile array
|
||||
*
|
||||
|
|
@ -215,28 +283,6 @@ class Profile
|
|||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all profile data of a local user
|
||||
*
|
||||
* If the viewer is an authenticated remote viewer, the profile displayed is the
|
||||
* one that has been configured for his/her viewing in the Contact manager.
|
||||
* Passing a non-zero profile ID can also allow a preview of a selected profile
|
||||
* by the owner
|
||||
*
|
||||
* Includes all available profile data
|
||||
*
|
||||
* @param string $nickname nick
|
||||
* @param int $uid uid
|
||||
* @param int $profile_id ID of the profile
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getByNickname($nickname, $uid = 0)
|
||||
{
|
||||
$profile = DBA::selectFirst('owner-view', [], ['nickname' => $nickname, 'uid' => $uid]);
|
||||
return $profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a profile for display in the sidebar.
|
||||
*
|
||||
|
|
@ -263,8 +309,20 @@ class Profile
|
|||
$o = '';
|
||||
$location = false;
|
||||
|
||||
// This function can also use contact information in $profile
|
||||
$is_contact = !empty($profile['cid']);
|
||||
// This function can also use contact information in $profile, but the 'cid'
|
||||
// value is going to be coming from 'owner-view', which means it's the wrong
|
||||
// contact ID for the user viewing this page. Use 'nurl' to look up the
|
||||
// correct contact table entry for the logged-in user.
|
||||
$profile_contact = [];
|
||||
|
||||
if (!empty($profile['nurl'])) {
|
||||
if (local_user() && ($profile['uid'] ?? 0) != local_user()) {
|
||||
$profile_contact = Contact::getByURL($profile['nurl'], null, ['rel'], local_user());
|
||||
}
|
||||
if (!empty($profile['cid']) && self::getMyURL()) {
|
||||
$profile_contact = Contact::selectFirst(['rel'], ['id' => $profile['cid']]);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($profile['nickname'])) {
|
||||
Logger::warning('Received profile with no nickname', ['profile' => $profile, 'callstack' => System::callstack(10)]);
|
||||
|
|
@ -287,21 +345,23 @@ class Profile
|
|||
$profile_url = DI::baseUrl()->get() . '/profile/' . $profile['nickname'];
|
||||
}
|
||||
|
||||
if (!empty($profile['id'])) {
|
||||
$cid = $profile['id'];
|
||||
} else {
|
||||
$cid = Contact::getIdForURL($profile_url, false);
|
||||
}
|
||||
|
||||
$follow_link = null;
|
||||
$unfollow_link = null;
|
||||
$subscribe_feed_link = null;
|
||||
$wallmessage_link = null;
|
||||
|
||||
// Who is the logged-in user to this profile?
|
||||
$visitor_contact = [];
|
||||
if (!empty($profile['uid']) && self::getMyURL()) {
|
||||
$visitor_contact = Contact::selectFirst(['rel'], ['uid' => $profile['uid'], 'nurl' => Strings::normaliseLink(self::getMyURL())]);
|
||||
}
|
||||
|
||||
$profile_contact = [];
|
||||
if (!empty($profile['cid']) && self::getMyURL()) {
|
||||
$profile_contact = Contact::selectFirst(['rel'], ['id' => $profile['cid']]);
|
||||
}
|
||||
|
||||
$profile_is_dfrn = $profile['network'] == Protocol::DFRN;
|
||||
$profile_is_native = in_array($profile['network'], Protocol::NATIVE_SUPPORT);
|
||||
$local_user_is_self = self::getMyURL() && ($profile['url'] == self::getMyURL());
|
||||
|
|
@ -332,17 +392,19 @@ class Profile
|
|||
$subscribe_feed_link = 'dfrn_poll/' . $profile['nickname'];
|
||||
}
|
||||
|
||||
if (Contact::canReceivePrivateMessages($profile)) {
|
||||
if (Contact::canReceivePrivateMessages($profile_contact)) {
|
||||
if ($visitor_is_followed || $visitor_is_following) {
|
||||
$wallmessage_link = $visitor_base_path . '/message/new/' . base64_encode($profile['addr'] ?? '');
|
||||
$wallmessage_link = $visitor_base_path . '/message/new/' . $profile_contact['id'];
|
||||
} elseif ($visitor_is_authenticated && !empty($profile['unkmail'])) {
|
||||
$wallmessage_link = 'wallmessage/' . $profile['nickname'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// show edit profile to yourself
|
||||
if (!$is_contact && $local_user_is_self) {
|
||||
// show edit profile to yourself, but only if this is not meant to be
|
||||
// rendered as a "contact". i.e., if 'self' (a "contact" table column) isn't
|
||||
// set in $profile.
|
||||
if (!isset($profile['self']) && $local_user_is_self) {
|
||||
$profile['edit'] = [DI::baseUrl() . '/settings/profile', DI::l10n()->t('Edit profile'), '', DI::l10n()->t('Edit profile')];
|
||||
$profile['menu'] = [
|
||||
'chg_photo' => DI::l10n()->t('Change profile photo'),
|
||||
|
|
@ -431,11 +493,9 @@ class Profile
|
|||
$p['address'] = BBCode::convert($p['address']);
|
||||
}
|
||||
|
||||
if (isset($p['photo'])) {
|
||||
$p['photo'] = ProxyUtils::proxifyUrl($p['photo'], false, ProxyUtils::SIZE_SMALL);
|
||||
}
|
||||
$p['photo'] = Contact::getAvatarUrlForId($cid, ProxyUtils::SIZE_SMALL);
|
||||
|
||||
$p['url'] = Contact::magicLink(($p['url'] ?? '') ?: $profile_url);
|
||||
$p['url'] = Contact::magicLinkById($cid);
|
||||
|
||||
$tpl = Renderer::getMarkupTemplate('profile/vcard.tpl');
|
||||
$o .= Renderer::replaceMacros($tpl, [
|
||||
|
|
@ -754,11 +814,11 @@ class Profile
|
|||
// Try to find the public contact entry of the visitor.
|
||||
$cid = Contact::getIdForURL($handle);
|
||||
if (!$cid) {
|
||||
Logger::log('unable to finger ' . $handle, Logger::DEBUG);
|
||||
Logger::info('Handle not found', ['handle' => $handle]);
|
||||
return [];
|
||||
}
|
||||
|
||||
$visitor = DBA::selectFirst('contact', [], ['id' => $cid]);
|
||||
$visitor = Contact::getById($cid);
|
||||
|
||||
// Authenticate the visitor.
|
||||
$_SESSION['authenticated'] = 1;
|
||||
|
|
@ -777,6 +837,19 @@ class Profile
|
|||
return $visitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the visitor cookies (see remote_user()) for signed HTTP requests
|
||||
* @return array Visitor contact array
|
||||
*/
|
||||
public static function addVisitorCookieForHTTPSigner()
|
||||
{
|
||||
$requester = HTTPSignature::getSigner('', $_SERVER);
|
||||
if (empty($requester)) {
|
||||
return [];
|
||||
}
|
||||
return Profile::addVisitorCookieForHandle($requester);
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenWebAuth authentication.
|
||||
*
|
||||
|
|
|
|||
113
src/Model/Storage/ExternalResource.php
Normal file
113
src/Model/Storage/ExternalResource.php
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2021, the Friendica project
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Model\Storage;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Friendica\Util\HTTPSignature;
|
||||
use Friendica\Network\IHTTPRequest;
|
||||
|
||||
/**
|
||||
* External resource storage class
|
||||
*
|
||||
* This class is used to load external resources, like images.
|
||||
* Is not intended to be selectable by admins as default storage class.
|
||||
*/
|
||||
class ExternalResource implements IStorage
|
||||
{
|
||||
const NAME = 'ExternalResource';
|
||||
|
||||
/** @var IHTTPRequest */
|
||||
private $httpRequest;
|
||||
|
||||
public function __construct(IHTTPRequest $httpRequest)
|
||||
{
|
||||
$this->httpRequest = $httpRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function get(string $reference)
|
||||
{
|
||||
$data = json_decode($reference);
|
||||
if (empty($data->url)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
$parts = parse_url($data->url);
|
||||
if (empty($parts['scheme']) || empty($parts['host'])) {
|
||||
return "";
|
||||
}
|
||||
|
||||
$fetchResult = HTTPSignature::fetchRaw($data->url, $data->uid);
|
||||
if ($fetchResult->isSuccess()) {
|
||||
return $fetchResult->getBody();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function put(string $data, string $reference = '')
|
||||
{
|
||||
throw new BadMethodCallException();
|
||||
}
|
||||
|
||||
public function delete(string $reference)
|
||||
{
|
||||
throw new BadMethodCallException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getOptions()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function saveOptions(array $data)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function getName()
|
||||
{
|
||||
return self::NAME;
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,8 @@ namespace Friendica\Model\Storage;
|
|||
|
||||
/**
|
||||
* Interface for storage backends
|
||||
*
|
||||
* @todo Split this interface into "IStorage" for get() operations (including Resource fetching) and "IUserStorage" for real user backends including put/delete/options
|
||||
*/
|
||||
interface IStorage
|
||||
{
|
||||
|
|
|
|||
|
|
@ -312,8 +312,8 @@ class User
|
|||
*/
|
||||
public static function getIdForURL(string $url)
|
||||
{
|
||||
// Avoid any database requests when the hostname isn't even part of the url.
|
||||
if (!strpos($url, DI::baseUrl()->getHostname())) {
|
||||
// Avoid database queries when the local node hostname isn't even part of the url.
|
||||
if (!Contact::isLocal($url)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -391,7 +391,12 @@ class User
|
|||
if (!DBA::exists('user', ['uid' => $uid]) || !$repairMissing) {
|
||||
return false;
|
||||
}
|
||||
Contact::createSelfFromUserId($uid);
|
||||
if (!DBA::exists('profile', ['uid' => $uid])) {
|
||||
DBA::insert('profile', ['uid' => $uid]);
|
||||
}
|
||||
if (!DBA::exists('contact', ['uid' => $uid, 'self' => true])) {
|
||||
Contact::createSelfFromUserId($uid);
|
||||
}
|
||||
$owner = self::getOwnerDataById($uid, false);
|
||||
}
|
||||
|
||||
|
|
@ -407,7 +412,7 @@ class User
|
|||
|
||||
// Check for correct url and normalised nurl
|
||||
$url = DI::baseUrl() . '/profile/' . $owner['nickname'];
|
||||
$repair = ($owner['url'] != $url) || ($owner['nurl'] != Strings::normaliseLink($owner['url']));
|
||||
$repair = empty($owner['network']) || ($owner['url'] != $url) || ($owner['nurl'] != Strings::normaliseLink($owner['url']));
|
||||
|
||||
if (!$repair) {
|
||||
// Check if "addr" is present and correct
|
||||
|
|
@ -1123,6 +1128,8 @@ class User
|
|||
Photo::update(['profile' => 1], ['resource-id' => $resource_id]);
|
||||
}
|
||||
}
|
||||
|
||||
Contact::updateSelfFromUserID($uid, true);
|
||||
}
|
||||
|
||||
Hook::callAll('register_account', $uid);
|
||||
|
|
@ -1131,6 +1138,42 @@ class User
|
|||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a user entry and distribute the changes if needed
|
||||
*
|
||||
* @param array $fields
|
||||
* @param integer $uid
|
||||
* @return boolean
|
||||
*/
|
||||
public static function update(array $fields, int $uid): bool
|
||||
{
|
||||
$old_owner = self::getOwnerDataById($uid);
|
||||
if (empty($old_owner)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!DBA::update('user', $fields, ['uid' => $uid])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$update = Contact::updateSelfFromUserID($uid);
|
||||
|
||||
$owner = self::getOwnerDataById($uid);
|
||||
if (empty($owner)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($old_owner['name'] != $owner['name']) {
|
||||
Profile::update(['name' => $owner['name']], $uid);
|
||||
}
|
||||
|
||||
if ($update) {
|
||||
Profile::publishUpdate($uid);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets block state for a given user
|
||||
*
|
||||
|
|
@ -1462,6 +1505,10 @@ class User
|
|||
*/
|
||||
public static function identities($uid)
|
||||
{
|
||||
if (empty($uid)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$identities = [];
|
||||
|
||||
$user = DBA::selectFirst('user', ['uid', 'nickname', 'username', 'parent-uid'], ['uid' => $uid]);
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ class Federation extends BaseAdmin
|
|||
'socialhome' => ['name' => 'SocialHome', 'color' => '#52056b'], // lilac from the Django Image used at the Socialhome homepage
|
||||
'wordpress' => ['name' => 'WordPress', 'color' => '#016087'], // Background color of the homepage
|
||||
'writefreely' => ['name' => 'WriteFreely', 'color' => '#292929'], // Font color of the homepage
|
||||
'mistpark' => ['name' => 'Nomad projects (Mistpark, Osada, Roadhouse, Zap)', 'color' => '#348a4a'], // Green like the Mistpark green
|
||||
'relay' => ['name' => 'ActivityPub Relay', 'color' => '#888888'], // Grey like the second color of the ActivityPub logo
|
||||
'other' => ['name' => DI::l10n()->t('Other'), 'color' => '#F1007E'], // ActivityPub main color
|
||||
];
|
||||
|
||||
|
|
@ -80,6 +82,10 @@ class Federation extends BaseAdmin
|
|||
|
||||
if (in_array($gserver['platform'], ['Red Matrix', 'redmatrix', 'red'])) {
|
||||
$version['version'] = 'Red ' . $version['version'];
|
||||
} elseif (in_array($gserver['platform'], ['osada', 'mistpark', 'roadhouse', 'zap'])) {
|
||||
$version['version'] = $gserver['platform'] . ' ' . $version['version'];
|
||||
} elseif (in_array($gserver['platform'], ['activityrelay', 'pub-relay', 'selective-relay', 'aoderelay'])) {
|
||||
$version['version'] = $gserver['platform'] . '-' . $version['version'];
|
||||
}
|
||||
|
||||
$versionCounts[] = $version;
|
||||
|
|
@ -92,12 +98,16 @@ class Federation extends BaseAdmin
|
|||
$platform = 'friendica';
|
||||
} elseif (in_array($platform, ['red matrix', 'redmatrix', 'red'])) {
|
||||
$platform = 'hubzilla';
|
||||
} elseif (in_array($platform, ['mistpark', 'osada', 'roadhouse', 'zap'])) {
|
||||
$platform = 'mistpark';
|
||||
} elseif(stristr($platform, 'pleroma')) {
|
||||
$platform = 'pleroma';
|
||||
} elseif(stristr($platform, 'statusnet')) {
|
||||
$platform = 'gnusocial';
|
||||
} elseif(stristr($platform, 'wordpress')) {
|
||||
$platform = 'wordpress';
|
||||
} elseif (in_array($platform, ['activityrelay', 'pub-relay', 'selective-relay', 'aoderelay'])) {
|
||||
$platform = 'relay';
|
||||
} elseif (!in_array($platform, $platforms)) {
|
||||
$platform = 'other';
|
||||
}
|
||||
|
|
@ -122,9 +132,17 @@ class Federation extends BaseAdmin
|
|||
$versionCounts = self::reformaPleromaVersions($versionCounts);
|
||||
} elseif ($platform == 'diaspora') {
|
||||
$versionCounts = self::reformaDiasporaVersions($versionCounts);
|
||||
} elseif ($platform == 'relay') {
|
||||
$versionCounts = self::reformatRelayVersions($versionCounts);
|
||||
} elseif (in_array($platform, ['funkwhale', 'mastodon', 'mobilizon', 'misskey'])) {
|
||||
$versionCounts = self::removeVersionSuffixes($versionCounts);
|
||||
}
|
||||
|
||||
$versionCounts = self::sortVersion($versionCounts);
|
||||
if (!in_array($platform, ['other', 'relay', 'mistpark'])) {
|
||||
$versionCounts = self::sortVersion($versionCounts);
|
||||
} else {
|
||||
ksort($versionCounts);
|
||||
}
|
||||
|
||||
$gserver['platform'] = $systems[$platform]['name'];
|
||||
|
||||
|
|
@ -250,6 +268,68 @@ class Federation extends BaseAdmin
|
|||
return $versionCounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up version numbers
|
||||
*
|
||||
* @param array $versionCounts list of version numbers
|
||||
* @return array with cleaned version numbers
|
||||
*/
|
||||
private static function removeVersionSuffixes(array $versionCounts)
|
||||
{
|
||||
$compacted = [];
|
||||
foreach ($versionCounts as $key => $value) {
|
||||
$version = $versionCounts[$key]['version'];
|
||||
|
||||
foreach ([' ', '+', '-', '#', '_', '~'] as $delimiter) {
|
||||
$parts = explode($delimiter, trim($version));
|
||||
$version = array_shift($parts);
|
||||
}
|
||||
|
||||
if (empty($compacted[$version])) {
|
||||
$compacted[$version] = $versionCounts[$key]['total'];
|
||||
} else {
|
||||
$compacted[$version] += $versionCounts[$key]['total'];
|
||||
}
|
||||
}
|
||||
|
||||
$versionCounts = [];
|
||||
foreach ($compacted as $version => $pl_total) {
|
||||
$versionCounts[] = ['version' => $version, 'total' => $pl_total];
|
||||
}
|
||||
|
||||
return $versionCounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up relay version numbers
|
||||
*
|
||||
* @param array $versionCounts list of version numbers
|
||||
* @return array with cleaned version numbers
|
||||
*/
|
||||
private static function reformatRelayVersions(array $versionCounts)
|
||||
{
|
||||
$compacted = [];
|
||||
foreach ($versionCounts as $key => $value) {
|
||||
$version = $versionCounts[$key]['version'];
|
||||
|
||||
$parts = explode(' ', trim($version));
|
||||
$version = array_shift($parts);
|
||||
|
||||
if (empty($compacted[$version])) {
|
||||
$compacted[$version] = $versionCounts[$key]['total'];
|
||||
} else {
|
||||
$compacted[$version] += $versionCounts[$key]['total'];
|
||||
}
|
||||
}
|
||||
|
||||
$versionCounts = [];
|
||||
foreach ($compacted as $version => $pl_total) {
|
||||
$versionCounts[] = ['version' => $version, 'total' => $pl_total];
|
||||
}
|
||||
|
||||
return $versionCounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reformat, sort and compact version numbers
|
||||
*
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ use Friendica\Model\Contact;
|
|||
use Friendica\Model\User;
|
||||
use Friendica\Module\BaseAdmin;
|
||||
use Friendica\Module\Register;
|
||||
use Friendica\Protocol\Relay;
|
||||
use Friendica\Util\BasePath;
|
||||
use Friendica\Util\EMailer\MailBuilder;
|
||||
use Friendica\Util\Strings;
|
||||
|
|
@ -208,8 +209,6 @@ class Site extends BaseAdmin
|
|||
$worker_fastlane = !empty($_POST['worker_fastlane']);
|
||||
|
||||
$relay_directly = !empty($_POST['relay_directly']);
|
||||
$relay_server = (!empty($_POST['relay_server']) ? Strings::escapeTags(trim($_POST['relay_server'])) : '');
|
||||
$relay_subscribe = !empty($_POST['relay_subscribe']);
|
||||
$relay_scope = (!empty($_POST['relay_scope']) ? Strings::escapeTags(trim($_POST['relay_scope'])) : '');
|
||||
$relay_server_tags = (!empty($_POST['relay_server_tags']) ? Strings::escapeTags(trim($_POST['relay_server_tags'])) : '');
|
||||
$relay_deny_tags = (!empty($_POST['relay_deny_tags']) ? Strings::escapeTags(trim($_POST['relay_deny_tags'])) : '');
|
||||
|
|
@ -418,8 +417,6 @@ class Site extends BaseAdmin
|
|||
DI::config()->set('system', 'worker_fastlane' , $worker_fastlane);
|
||||
|
||||
DI::config()->set('system', 'relay_directly' , $relay_directly);
|
||||
DI::config()->set('system', 'relay_server' , $relay_server);
|
||||
DI::config()->set('system', 'relay_subscribe' , $relay_subscribe);
|
||||
DI::config()->set('system', 'relay_scope' , $relay_scope);
|
||||
DI::config()->set('system', 'relay_server_tags', $relay_server_tags);
|
||||
DI::config()->set('system', 'relay_deny_tags' , $relay_deny_tags);
|
||||
|
|
@ -589,6 +586,10 @@ class Site extends BaseAdmin
|
|||
'$performance' => DI::l10n()->t('Performance'),
|
||||
'$worker_title' => DI::l10n()->t('Worker'),
|
||||
'$relay_title' => DI::l10n()->t('Message Relay'),
|
||||
'$relay_description' => DI::l10n()->t('Use the command "console relay" in the command line to add or remove relays.'),
|
||||
'$no_relay_list' => DI::l10n()->t('The system is not subscribed to any relays at the moment.'),
|
||||
'$relay_list_title' => DI::l10n()->t('The system is currently subscribed to the following relays:'),
|
||||
'$relay_list' => Relay::getList(['url']),
|
||||
'$relocate' => DI::l10n()->t('Relocate Instance'),
|
||||
'$relocate_warning' => DI::l10n()->t('<strong>Warning!</strong> Advanced function. Could make this server unreachable.'),
|
||||
'$baseurl' => DI::baseUrl()->get(true),
|
||||
|
|
@ -688,10 +689,8 @@ class Site extends BaseAdmin
|
|||
'$worker_queues' => ['worker_queues', DI::l10n()->t('Maximum number of parallel workers'), DI::config()->get('system', 'worker_queues'), DI::l10n()->t('On shared hosters set this to %d. On larger systems, values of %d are great. Default value is %d.', 5, 20, 10)],
|
||||
'$worker_fastlane' => ['worker_fastlane', DI::l10n()->t('Enable fastlane'), DI::config()->get('system', 'worker_fastlane'), DI::l10n()->t('When enabed, the fastlane mechanism starts an additional worker if processes with higher priority are blocked by processes of lower priority.')],
|
||||
|
||||
'$relay_subscribe' => ['relay_subscribe', DI::l10n()->t('Use relay servers'), DI::config()->get('system', 'relay_subscribe'), DI::l10n()->t('Enables the receiving of public posts from relay servers. They will be included in the search, subscribed tags and on the global community page.')],
|
||||
'$relay_server' => ['relay_server', DI::l10n()->t('"Social Relay" server'), DI::config()->get('system', 'relay_server'), DI::l10n()->t('Address of the "Social Relay" server where public posts should be send to. For example %s. ActivityRelay servers are administrated via the "console relay" command line command.', 'https://social-relay.isurf.ca')],
|
||||
'$relay_directly' => ['relay_directly', DI::l10n()->t('Direct relay transfer'), DI::config()->get('system', 'relay_directly'), DI::l10n()->t('Enables the direct transfer to other servers without using the relay servers')],
|
||||
'$relay_scope' => ['relay_scope', DI::l10n()->t('Relay scope'), DI::config()->get('system', 'relay_scope'), DI::l10n()->t('Can be "all" or "tags". "all" means that every public post should be received. "tags" means that only posts with selected tags should be received.'), ['' => DI::l10n()->t('Disabled'), 'all' => DI::l10n()->t('all'), 'tags' => DI::l10n()->t('tags')]],
|
||||
'$relay_scope' => ['relay_scope', DI::l10n()->t('Relay scope'), DI::config()->get('system', 'relay_scope'), DI::l10n()->t('Can be "all" or "tags". "all" means that every public post should be received. "tags" means that only posts with selected tags should be received.'), [SR_SCOPE_NONE => DI::l10n()->t('Disabled'), SR_SCOPE_ALL => DI::l10n()->t('all'), SR_SCOPE_TAGS => DI::l10n()->t('tags')]],
|
||||
'$relay_server_tags' => ['relay_server_tags', DI::l10n()->t('Server tags'), DI::config()->get('system', 'relay_server_tags'), DI::l10n()->t('Comma separated list of tags for the "tags" subscription.')],
|
||||
'$relay_deny_tags' => ['relay_deny_tags', DI::l10n()->t('Deny Server tags'), DI::config()->get('system', 'relay_deny_tags'), DI::l10n()->t('Comma separated list of tags that are rejected.')],
|
||||
'$relay_user_tags' => ['relay_user_tags', DI::l10n()->t('Allow user tags'), DI::config()->get('system', 'relay_user_tags'), DI::l10n()->t('If enabled, the tags from the saved searches will used for the "tags" subscription in addition to the "relay_server_tags".')],
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ class Summary extends BaseAdmin
|
|||
];
|
||||
|
||||
$users = 0;
|
||||
$pageFlagsCountStmt = DBA::p('SELECT `page-flags`, COUNT(`uid`) AS `count` FROM `user` GROUP BY `page-flags`');
|
||||
$pageFlagsCountStmt = DBA::p('SELECT `page-flags`, COUNT(`uid`) AS `count` FROM `user` WHERE `uid` != ? GROUP BY `page-flags`', 0);
|
||||
while ($pageFlagsCount = DBA::fetch($pageFlagsCountStmt)) {
|
||||
$accounts[$pageFlagsCount['page-flags']][1] = $pageFlagsCount['count'];
|
||||
$users += $pageFlagsCount['count'];
|
||||
|
|
|
|||
|
|
@ -35,16 +35,15 @@ class Index extends BaseApi
|
|||
{
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
if (self::login(self::SCOPE_READ) === false) {
|
||||
throw new HTTPException\ForbiddenException();
|
||||
}
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
$request = self::getRequest([
|
||||
'since_id' => 0,
|
||||
'count' => 0,
|
||||
]);
|
||||
|
||||
$condition = ["`id` > ? AND `uid` = ?", $request['since_id'], self::$current_user_id];
|
||||
$condition = ["`id` > ? AND `uid` = ?", $request['since_id'], $uid];
|
||||
$params = ['limit' => $request['count']];
|
||||
$events = DBA::selectToArray('event', [], $condition, $params);
|
||||
|
||||
|
|
|
|||
|
|
@ -37,16 +37,15 @@ class Show extends BaseApi
|
|||
{
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
if (self::login(self::SCOPE_READ) === false) {
|
||||
throw new HTTPException\ForbiddenException();
|
||||
}
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
// retrieve general information about profiles for user
|
||||
$directory = DI::config()->get('system', 'directory');
|
||||
|
||||
$profile = Profile::getByUID(self::$current_user_id);
|
||||
$profile = Profile::getByUID($uid);
|
||||
|
||||
$profileFields = DI::profileField()->select(['uid' => self::$current_user_id, 'psid' => PermissionSet::PUBLIC]);
|
||||
$profileFields = DI::profileField()->select(['uid' => $uid, 'psid' => PermissionSet::PUBLIC]);
|
||||
|
||||
$profile = self::formatProfile($profile, $profileFields);
|
||||
|
||||
|
|
@ -58,7 +57,7 @@ class Show extends BaseApi
|
|||
}
|
||||
|
||||
// return settings, authenticated user and profiles data
|
||||
$self = Contact::selectFirst(['nurl'], ['uid' => self::$current_user_id, 'self' => true]);
|
||||
$self = Contact::selectFirst(['nurl'], ['uid' => $uid, 'self' => true]);
|
||||
|
||||
$result = [
|
||||
'multi_profiles' => false,
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ namespace Friendica\Module\Api\Mastodon;
|
|||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Module\BaseApi;
|
||||
|
||||
/**
|
||||
|
|
@ -37,16 +38,27 @@ class Accounts extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
if (empty($parameters['id'])) {
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id']) && empty($parameters['name'])) {
|
||||
DI::mstdnError()->UnprocessableEntity();
|
||||
}
|
||||
|
||||
$id = $parameters['id'];
|
||||
if (!DBA::exists('contact', ['id' => $id, 'uid' => 0])) {
|
||||
DI::mstdnError()->RecordNotFound();
|
||||
if (!empty($parameters['id'])) {
|
||||
$id = $parameters['id'];
|
||||
if (!DBA::exists('contact', ['id' => $id, 'uid' => 0])) {
|
||||
DI::mstdnError()->RecordNotFound();
|
||||
}
|
||||
} else {
|
||||
$contact = Contact::selectFirst(['id'], ['nick' => $parameters['name'], 'uid' => 0]);
|
||||
if (!empty($contact['id'])) {
|
||||
$id = $contact['id'];
|
||||
} elseif (!($id = Contact::getIdForURL($parameters['name'], 0, false))) {
|
||||
DI::mstdnError()->RecordNotFound();
|
||||
}
|
||||
}
|
||||
|
||||
$account = DI::mstdnAccount()->createFromContactId($id, self::getCurrentUserID());
|
||||
$account = DI::mstdnAccount()->createFromContactId($id, $uid);
|
||||
System::jsonExit($account);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class Block extends BaseApi
|
|||
{
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_FOLLOW);
|
||||
self::checkAllowedScope(self::SCOPE_FOLLOW);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
|
|||
|
|
@ -33,14 +33,14 @@ class Follow extends BaseApi
|
|||
{
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_FOLLOW);
|
||||
self::checkAllowedScope(self::SCOPE_FOLLOW);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
DI::mstdnError()->UnprocessableEntity();
|
||||
}
|
||||
|
||||
$cid = Contact::follow($parameters['id'], self::getCurrentUserID());
|
||||
$cid = Contact::follow($parameters['id'], $uid);
|
||||
|
||||
System::jsonExit(DI::mstdnRelationship()->createFromContactId($cid, $uid)->toArray());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class Followers extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
@ -54,30 +54,31 @@ class Followers extends BaseApi
|
|||
$request = self::getRequest([
|
||||
'max_id' => 0, // Return results older than this id
|
||||
'since_id' => 0, // Return results newer than this id
|
||||
'limit' => 20, // Maximum number of results to return. Defaults to 20.
|
||||
'limit' => 40, // Maximum number of results to return. Defaults to 40.
|
||||
]);
|
||||
|
||||
$params = ['order' => ['cid' => true], 'limit' => $request['limit']];
|
||||
$params = ['order' => ['relation-cid' => true], 'limit' => $request['limit']];
|
||||
|
||||
$condition = ['relation-cid' => $id, 'follows' => true];
|
||||
$condition = ['cid' => $id, 'follows' => true];
|
||||
|
||||
if (!empty($request['max_id'])) {
|
||||
$condition = DBA::mergeConditions($condition, ["`cid` < ?", $request['max_id']]);
|
||||
$condition = DBA::mergeConditions($condition, ["`relation-cid` < ?", $request['max_id']]);
|
||||
}
|
||||
|
||||
if (!empty($request['since_id'])) {
|
||||
$condition = DBA::mergeConditions($condition, ["`cid` > ?", $request['since_id']]);
|
||||
$condition = DBA::mergeConditions($condition, ["`relation-cid` > ?", $request['since_id']]);
|
||||
}
|
||||
|
||||
if (!empty($min_id)) {
|
||||
$condition = DBA::mergeConditions($condition, ["`cid` > ?", $min_id]);
|
||||
$condition = DBA::mergeConditions($condition, ["`relation-cid` > ?", $min_id]);
|
||||
|
||||
$params['order'] = ['cid'];
|
||||
}
|
||||
|
||||
$followers = DBA::select('contact-relation', ['cid'], $condition, $parameters);
|
||||
$followers = DBA::select('contact-relation', ['relation-cid'], $condition, $parameters);
|
||||
while ($follower = DBA::fetch($followers)) {
|
||||
$accounts[] = DI::mstdnAccount()->createFromContactId($follower['cid'], $uid);
|
||||
self::setBoundaries($follower['relation-cid']);
|
||||
$accounts[] = DI::mstdnAccount()->createFromContactId($follower['relation-cid'], $uid);
|
||||
}
|
||||
DBA::close($followers);
|
||||
|
||||
|
|
@ -85,6 +86,7 @@ class Followers extends BaseApi
|
|||
array_reverse($accounts);
|
||||
}
|
||||
|
||||
self::setLinkHeader();
|
||||
System::jsonExit($accounts);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class Following extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
@ -57,27 +57,28 @@ class Following extends BaseApi
|
|||
'limit' => 40, // Maximum number of results to return. Defaults to 40.
|
||||
]);
|
||||
|
||||
$params = ['order' => ['relation-cid' => true], 'limit' => $request['limit']];
|
||||
$params = ['order' => ['cid' => true], 'limit' => $request['limit']];
|
||||
|
||||
$condition = ['cid' => $id, 'follows' => true];
|
||||
$condition = ['relation-cid' => $id, 'follows' => true];
|
||||
|
||||
if (!empty($request['max_id'])) {
|
||||
$condition = DBA::mergeConditions($condition, ["`relation-cid` < ?", $request['max_id']]);
|
||||
$condition = DBA::mergeConditions($condition, ["`cid` < ?", $request['max_id']]);
|
||||
}
|
||||
|
||||
if (!empty($request['since_id'])) {
|
||||
$condition = DBA::mergeConditions($condition, ["`relation-cid` > ?", $request['since_id']]);
|
||||
$condition = DBA::mergeConditions($condition, ["`cid` > ?", $request['since_id']]);
|
||||
}
|
||||
|
||||
if (!empty($min_id)) {
|
||||
$condition = DBA::mergeConditions($condition, ["`relation-cid` > ?", $min_id]);
|
||||
$condition = DBA::mergeConditions($condition, ["`cid` > ?", $min_id]);
|
||||
|
||||
$params['order'] = ['cid'];
|
||||
}
|
||||
|
||||
$followers = DBA::select('contact-relation', ['relation-cid'], $condition, $parameters);
|
||||
$followers = DBA::select('contact-relation', ['cid'], $condition, $parameters);
|
||||
while ($follower = DBA::fetch($followers)) {
|
||||
$accounts[] = DI::mstdnAccount()->createFromContactId($follower['relation-cid'], $uid);
|
||||
self::setBoundaries($follower['cid']);
|
||||
$accounts[] = DI::mstdnAccount()->createFromContactId($follower['cid'], $uid);
|
||||
}
|
||||
DBA::close($followers);
|
||||
|
||||
|
|
@ -85,6 +86,7 @@ class Following extends BaseApi
|
|||
array_reverse($accounts);
|
||||
}
|
||||
|
||||
self::setLinkHeader();
|
||||
System::jsonExit($accounts);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class IdentityProofs extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
|
||||
System::jsonExit([]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class Lists extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class Mute extends BaseApi
|
|||
{
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_FOLLOW);
|
||||
self::checkAllowedScope(self::SCOPE_FOLLOW);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class Note extends BaseApi
|
|||
{
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_WRITE);
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
|
|||
|
|
@ -37,17 +37,21 @@ class Relationships extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
$request = self::getRequest([
|
||||
'id' => [],
|
||||
]);
|
||||
|
||||
if (empty($request['id']) || !is_array($request['id'])) {
|
||||
if (empty($request['id'])) {
|
||||
DI::mstdnError()->UnprocessableEntity();
|
||||
}
|
||||
|
||||
if (!is_array($request['id'])) {
|
||||
$request['id'] = [$request['id']];
|
||||
}
|
||||
|
||||
$relationsships = [];
|
||||
|
||||
foreach ($request['id'] as $id) {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class Search extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
$request = self::getRequest([
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ class Statuses extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
DI::mstdnError()->UnprocessableEntity();
|
||||
}
|
||||
|
|
@ -66,8 +68,6 @@ class Statuses extends BaseApi
|
|||
|
||||
$params = ['order' => ['uri-id' => true], 'limit' => $request['limit']];
|
||||
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (!$uid) {
|
||||
$condition = ['author-id' => $id, 'private' => [Item::PUBLIC, Item::UNLISTED],
|
||||
'uid' => 0, 'network' => Protocol::FEDERATED];
|
||||
|
|
@ -108,6 +108,7 @@ class Statuses extends BaseApi
|
|||
|
||||
$statuses = [];
|
||||
while ($item = Post::fetch($items)) {
|
||||
self::setBoundaries($item['uri-id']);
|
||||
$statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid);
|
||||
}
|
||||
DBA::close($items);
|
||||
|
|
@ -116,6 +117,7 @@ class Statuses extends BaseApi
|
|||
array_reverse($statuses);
|
||||
}
|
||||
|
||||
self::setLinkHeader();
|
||||
System::jsonExit($statuses);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class Unblock extends BaseApi
|
|||
{
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_FOLLOW);
|
||||
self::checkAllowedScope(self::SCOPE_FOLLOW);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class Unfollow extends BaseApi
|
|||
{
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_FOLLOW);
|
||||
self::checkAllowedScope(self::SCOPE_FOLLOW);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class Unmute extends BaseApi
|
|||
{
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_FOLLOW);
|
||||
self::checkAllowedScope(self::SCOPE_FOLLOW);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
|
|||
|
|
@ -21,8 +21,9 @@
|
|||
|
||||
namespace Friendica\Module\Api\Mastodon\Accounts;
|
||||
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Module\BaseApi;
|
||||
use Friendica\Util\Network;
|
||||
use Friendica\Util\HTTPInputData;
|
||||
|
||||
/**
|
||||
* @see https://docs.joinmastodon.org/methods/accounts/
|
||||
|
|
@ -31,12 +32,13 @@ class UpdateCredentials extends BaseApi
|
|||
{
|
||||
public static function patch(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_WRITE);
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
$data = Network::postdata();
|
||||
$data = HTTPInputData::process();
|
||||
|
||||
Logger::info('Patch data', ['data' => $data]);
|
||||
|
||||
// @todo Parse the raw data that is in the "multipart/form-data" format
|
||||
self::unsupported('patch');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class VerifyCredentials extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
$self = User::getOwnerDataById($uid);
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class Announcements extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
|
||||
// @todo Possibly use the message from the pageheader addon for this
|
||||
System::jsonExit([]);
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ class Apps extends BaseApi
|
|||
if (!empty($postdata)) {
|
||||
$postrequest = json_decode($postdata, true);
|
||||
if (!empty($postrequest) && is_array($postrequest)) {
|
||||
$request = array_merge($request, $$postrequest);
|
||||
$request = array_merge($request, $postrequest);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -80,6 +80,6 @@ class Apps extends BaseApi
|
|||
DI::mstdnError()->InternalError();
|
||||
}
|
||||
|
||||
System::jsonExit(DI::mstdnApplication()->createFromApplicationId(DBA::lastInsertId()));
|
||||
System::jsonExit(DI::mstdnApplication()->createFromApplicationId(DBA::lastInsertId())->toArray());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,13 +32,13 @@ class VerifyCredentials extends BaseApi
|
|||
{
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$application = self::getCurrentApplication();
|
||||
|
||||
if (empty($application['id'])) {
|
||||
DI::mstdnError()->Unauthorized();
|
||||
}
|
||||
|
||||
System::jsonExit($application['id']);
|
||||
System::jsonExit(DI::mstdnApplication()->createFromApplicationId($application['id']));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class Blocks extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
@ -77,6 +77,7 @@ class Blocks extends BaseApi
|
|||
|
||||
$followers = DBA::select('user-contact', ['cid'], $condition, $parameters);
|
||||
while ($follower = DBA::fetch($followers)) {
|
||||
self::setBoundaries($follower['cid']);
|
||||
$accounts[] = DI::mstdnAccount()->createFromContactId($follower['cid'], $uid);
|
||||
}
|
||||
DBA::close($followers);
|
||||
|
|
@ -85,6 +86,7 @@ class Blocks extends BaseApi
|
|||
array_reverse($accounts);
|
||||
}
|
||||
|
||||
self::setLinkHeader();
|
||||
System::jsonExit($accounts);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ class Bookmarks extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
$request = self::getRequest([
|
||||
|
|
@ -52,7 +52,7 @@ class Bookmarks extends BaseApi
|
|||
|
||||
$params = ['order' => ['uri-id' => true], 'limit' => $request['limit']];
|
||||
|
||||
$condition = ['pinned' => true, 'uid' => $uid];
|
||||
$condition = ['starred' => true, 'uid' => $uid];
|
||||
|
||||
if (!empty($request['max_id'])) {
|
||||
$condition = DBA::mergeConditions($condition, ["`uri-id` < ?", $request['max_id']]);
|
||||
|
|
@ -72,6 +72,7 @@ class Bookmarks extends BaseApi
|
|||
|
||||
$statuses = [];
|
||||
while ($item = Post::fetch($items)) {
|
||||
self::setBoundaries($item['uri-id']);
|
||||
$statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid);
|
||||
}
|
||||
DBA::close($items);
|
||||
|
|
@ -80,6 +81,7 @@ class Bookmarks extends BaseApi
|
|||
array_reverse($statuses);
|
||||
}
|
||||
|
||||
self::setLinkHeader();
|
||||
System::jsonExit($statuses);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,11 +29,11 @@ use Friendica\Module\BaseApi;
|
|||
/**
|
||||
* @see https://docs.joinmastodon.org/methods/timelines/conversations/
|
||||
*/
|
||||
class Conversation extends BaseApi
|
||||
class Conversations extends BaseApi
|
||||
{
|
||||
public static function delete(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_WRITE);
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (!empty($parameters['id'])) {
|
||||
|
|
@ -52,7 +52,7 @@ class Conversation extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
$request = self::getRequest([
|
||||
|
|
@ -85,6 +85,7 @@ class Conversation extends BaseApi
|
|||
$conversations = [];
|
||||
|
||||
while ($conv = DBA::fetch($convs)) {
|
||||
self::setBoundaries($conv['id']);
|
||||
$conversations[] = DI::mstdnConversation()->CreateFromConvId($conv['id']);
|
||||
}
|
||||
|
||||
|
|
@ -94,6 +95,7 @@ class Conversation extends BaseApi
|
|||
array_reverse($conversations);
|
||||
}
|
||||
|
||||
self::setLinkHeader();
|
||||
System::jsonExit($conversations);
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Module\Api\Mastodon\Conversation;
|
||||
namespace Friendica\Module\Api\Mastodon\Conversations;
|
||||
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
|
|
@ -33,7 +33,7 @@ class Read extends BaseApi
|
|||
{
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_WRITE);
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (!empty($parameters['id'])) {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class Favourited extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
// @todo provide HTTP link header
|
||||
|
|
@ -70,6 +70,7 @@ class Favourited extends BaseApi
|
|||
|
||||
$statuses = [];
|
||||
while ($item = Post::fetch($items)) {
|
||||
self::setBoundaries($item['thr-parent-id']);
|
||||
$statuses[] = DI::mstdnStatus()->createFromUriId($item['thr-parent-id'], $uid);
|
||||
}
|
||||
DBA::close($items);
|
||||
|
|
@ -78,6 +79,7 @@ class Favourited extends BaseApi
|
|||
array_reverse($statuses);
|
||||
}
|
||||
|
||||
self::setLinkHeader();
|
||||
System::jsonExit($statuses);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,12 +29,21 @@ use Friendica\Module\BaseApi;
|
|||
*/
|
||||
class Filters extends BaseApi
|
||||
{
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
|
||||
self::unsupported('post');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $parameters
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
System::jsonError(404, ['error' => 'Record not found']);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
|
||||
System::jsonExit([]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class FollowRequests extends BaseApi
|
|||
*/
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_FOLLOW);
|
||||
self::checkAllowedScope(self::SCOPE_FOLLOW);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
$introduction = DI::intro()->selectFirst(['id' => $parameters['id'], 'uid' => $uid]);
|
||||
|
|
@ -83,7 +83,7 @@ class FollowRequests extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
$request = self::getRequest([
|
||||
|
|
@ -92,8 +92,6 @@ class FollowRequests extends BaseApi
|
|||
'limit' => 40, // Maximum number of results to return. Defaults to 40. Paginate using the HTTP Link header.
|
||||
]);
|
||||
|
||||
$baseUrl = DI::baseUrl();
|
||||
|
||||
$introductions = DI::intro()->selectByBoundaries(
|
||||
['`uid` = ? AND NOT `ignore`', $uid],
|
||||
['order' => ['id' => 'DESC']],
|
||||
|
|
@ -106,6 +104,7 @@ class FollowRequests extends BaseApi
|
|||
|
||||
foreach ($introductions as $key => $introduction) {
|
||||
try {
|
||||
self::setBoundaries($introduction->id);
|
||||
$return[] = DI::mstdnFollowRequest()->createFromIntroduction($introduction);
|
||||
} catch (HTTPException\InternalServerErrorException $exception) {
|
||||
DI::intro()->delete($introduction);
|
||||
|
|
@ -113,22 +112,7 @@ class FollowRequests extends BaseApi
|
|||
}
|
||||
}
|
||||
|
||||
$base_query = [];
|
||||
if (isset($_GET['limit'])) {
|
||||
$base_query['limit'] = $request['limit'];
|
||||
}
|
||||
|
||||
$links = [];
|
||||
if ($introductions->getTotalCount() > $request['limit']) {
|
||||
$links[] = '<' . $baseUrl->get() . '/api/v1/follow_requests?' . http_build_query($base_query + ['max_id' => $introductions[count($introductions) - 1]->id]) . '>; rel="next"';
|
||||
}
|
||||
|
||||
if (count($introductions)) {
|
||||
$links[] = '<' . $baseUrl->get() . '/api/v1/follow_requests?' . http_build_query($base_query + ['min_id' => $introductions[0]->id]) . '>; rel="prev"';
|
||||
}
|
||||
|
||||
header('Link: ' . implode(', ', $links));
|
||||
|
||||
self::setLinkHeader();
|
||||
System::jsonExit($return);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
59
src/Module/Api/Mastodon/Instance/Rules.php
Normal file
59
src/Module/Api/Mastodon/Instance/Rules.php
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2021, the Friendica project
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Module\Api\Mastodon\Instance;
|
||||
|
||||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Content\Text\HTML;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\DI;
|
||||
use Friendica\Module\BaseApi;
|
||||
use Friendica\Network\HTTPException;
|
||||
|
||||
/**
|
||||
* Undocumented API endpoint
|
||||
*/
|
||||
class Rules extends BaseApi
|
||||
{
|
||||
/**
|
||||
* @param array $parameters
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
$rules = [];
|
||||
$id = 0;
|
||||
|
||||
if (DI::config()->get('system', 'tosdisplay')) {
|
||||
$html = BBCode::convert(DI::config()->get('system', 'tostext'), false, BBCode::EXTERNAL);
|
||||
|
||||
$msg = HTML::toPlaintext($html, 0, true);
|
||||
foreach (explode("\n", $msg) as $line) {
|
||||
$line = trim($line);
|
||||
if ($line) {
|
||||
$rules[] = ['id' => (string)++$id, 'text' => $line];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
System::jsonExit($rules);
|
||||
}
|
||||
}
|
||||
|
|
@ -33,8 +33,7 @@ class Lists extends BaseApi
|
|||
{
|
||||
public static function delete(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_WRITE);
|
||||
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
@ -54,9 +53,8 @@ class Lists extends BaseApi
|
|||
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_WRITE);
|
||||
|
||||
$uid = self::getCurrentUserID();
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
$request = self::getRequest([
|
||||
'title' => '',
|
||||
|
|
@ -78,13 +76,16 @@ class Lists extends BaseApi
|
|||
|
||||
public static function put(array $parameters = [])
|
||||
{
|
||||
$data = self::getPutData();
|
||||
$request = self::getRequest([
|
||||
'title' => '', // The title of the list to be updated.
|
||||
'replies_policy' => '', // One of: "followed", "list", or "none".
|
||||
]);
|
||||
|
||||
if (empty($data['title']) || empty($parameters['id'])) {
|
||||
if (empty($request['title']) || empty($parameters['id'])) {
|
||||
DI::mstdnError()->UnprocessableEntity();
|
||||
}
|
||||
|
||||
Group::update($parameters['id'], $data['title']);
|
||||
Group::update($parameters['id'], $request['title']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -93,7 +94,7 @@ class Lists extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class Accounts extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
@ -95,6 +95,7 @@ class Accounts extends BaseApi
|
|||
|
||||
$members = DBA::select('group_member', ['contact-id'], $condition, $params);
|
||||
while ($member = DBA::fetch($members)) {
|
||||
self::setBoundaries($member['contact-id']);
|
||||
$accounts[] = DI::mstdnAccount()->createFromContactId($member['contact-id'], $uid);
|
||||
}
|
||||
DBA::close($members);
|
||||
|
|
@ -103,6 +104,7 @@ class Accounts extends BaseApi
|
|||
array_reverse($accounts);
|
||||
}
|
||||
|
||||
self::setLinkHeader();
|
||||
System::jsonExit($accounts);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class Markers extends BaseApi
|
|||
{
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_WRITE);
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
|
||||
self::unsupported('post');
|
||||
}
|
||||
|
|
@ -42,7 +42,7 @@ class Markers extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
|
||||
System::jsonExit([]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class Media extends BaseApi
|
|||
{
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_WRITE);
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
Logger::info('Photo post', ['request' => $_REQUEST, 'files' => $_FILES]);
|
||||
|
|
@ -55,10 +55,15 @@ class Media extends BaseApi
|
|||
|
||||
public static function put(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_WRITE);
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
$data = self::getPutData();
|
||||
$request = self::getRequest([
|
||||
'file' => [], // The file to be attached, using multipart form data.
|
||||
'thumbnail' => [], // The custom thumbnail of the media to be attached, using multipart form data.
|
||||
'description' => '', // A plain-text description of the media, for accessibility purposes.
|
||||
'focus' => '', // Two floating points (x,y), comma-delimited ranging from -1.0 to 1.0
|
||||
]);
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
DI::mstdnError()->UnprocessableEntity();
|
||||
|
|
@ -69,7 +74,7 @@ class Media extends BaseApi
|
|||
DI::mstdnError()->RecordNotFound();
|
||||
}
|
||||
|
||||
Photo::update(['desc' => $data['description'] ?? ''], ['resource-id' => $photo['resource-id']]);
|
||||
Photo::update(['desc' => $request['description']], ['resource-id' => $photo['resource-id']]);
|
||||
|
||||
System::jsonExit(DI::mstdnAttachment()->createFromPhoto($parameters['id']));
|
||||
}
|
||||
|
|
@ -80,7 +85,7 @@ class Media extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class Mutes extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
@ -77,6 +77,7 @@ class Mutes extends BaseApi
|
|||
|
||||
$followers = DBA::select('user-contact', ['cid'], $condition, $parameters);
|
||||
while ($follower = DBA::fetch($followers)) {
|
||||
self::setBoundaries($follower['cid']);
|
||||
$accounts[] = DI::mstdnAccount()->createFromContactId($follower['cid'], $uid);
|
||||
}
|
||||
DBA::close($followers);
|
||||
|
|
@ -85,6 +86,7 @@ class Mutes extends BaseApi
|
|||
array_reverse($accounts);
|
||||
}
|
||||
|
||||
self::setLinkHeader();
|
||||
System::jsonExit($accounts);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,8 +25,10 @@ use Friendica\Core\System;
|
|||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Notification;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Model\Verb;
|
||||
use Friendica\Module\BaseApi;
|
||||
use Friendica\Protocol\Activity;
|
||||
|
||||
/**
|
||||
* @see https://docs.joinmastodon.org/methods/notifications/
|
||||
|
|
@ -39,15 +41,15 @@ class Notifications extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (!empty($parameters['id'])) {
|
||||
$id = $parameters['id'];
|
||||
if (!DBA::exists('notify', ['id' => $id, 'uid' => $uid])) {
|
||||
if (!DBA::exists('notification', ['id' => $id, 'uid' => $uid])) {
|
||||
DI::mstdnError()->RecordNotFound();
|
||||
}
|
||||
System::jsonExit(DI::mstdnNotification()->createFromNotifyId($id));
|
||||
System::jsonExit(DI::mstdnNotification()->createFromNotificationId($id));
|
||||
}
|
||||
|
||||
$request = self::getRequest([
|
||||
|
|
@ -63,7 +65,7 @@ class Notifications extends BaseApi
|
|||
|
||||
$params = ['order' => ['id' => true], 'limit' => $request['limit']];
|
||||
|
||||
$condition = ['uid' => $uid, 'seen' => false, 'type' => []];
|
||||
$condition = ['uid' => $uid, 'seen' => false];
|
||||
|
||||
if (!empty($request['account_id'])) {
|
||||
$contact = Contact::getById($request['account_id'], ['url']);
|
||||
|
|
@ -72,17 +74,40 @@ class Notifications extends BaseApi
|
|||
}
|
||||
}
|
||||
|
||||
if (!in_array('follow_request', $request['exclude_types'])) {
|
||||
$condition['type'] = array_merge($condition['type'], [Notification\Type::INTRO]);
|
||||
if (in_array('follow_request', $request['exclude_types'])) {
|
||||
$condition = DBA::mergeConditions($condition,
|
||||
["(`vid` != ? OR `type` != ? OR NOT EXISTS (SELECT `id` FROM `contact` WHERE `id` = `actor-id` AND `pending`))",
|
||||
Verb::getID(Activity::FOLLOW), Post\UserNotification::NOTIF_NONE]);
|
||||
}
|
||||
|
||||
if (!in_array('mention', $request['exclude_types'])) {
|
||||
$condition['type'] = array_merge($condition['type'],
|
||||
[Notification\Type::WALL, Notification\Type::COMMENT, Notification\Type::MAIL, Notification\Type::TAG_SELF, Notification\Type::POKE]);
|
||||
if (in_array('follow', $request['exclude_types'])) {
|
||||
$condition = DBA::mergeConditions($condition,
|
||||
["(`vid` != ? OR `type` != ? OR NOT EXISTS (SELECT `id` FROM `contact` WHERE `id` = `actor-id` AND NOT `pending`))",
|
||||
Verb::getID(Activity::FOLLOW), Post\UserNotification::NOTIF_NONE]);
|
||||
}
|
||||
|
||||
if (!in_array('status', $request['exclude_types'])) {
|
||||
$condition['type'] = array_merge($condition['type'], [Notification\Type::SHARE]);
|
||||
if (in_array('favourite', $request['exclude_types'])) {
|
||||
$condition = DBA::mergeConditions($condition, ["(NOT `vid` IN (?, ?) OR NOT `type` IN (?, ?))",
|
||||
Verb::getID(Activity::LIKE), Verb::getID(Activity::DISLIKE),
|
||||
Post\UserNotification::NOTIF_DIRECT_COMMENT, Post\UserNotification::NOTIF_THREAD_COMMENT]);
|
||||
}
|
||||
|
||||
if (in_array('reblog', $request['exclude_types'])) {
|
||||
$condition = DBA::mergeConditions($condition, ["(NOT `vid` IN (?) OR NOT `type` IN (?, ?))",
|
||||
Verb::getID(Activity::ANNOUNCE),
|
||||
Post\UserNotification::NOTIF_DIRECT_COMMENT, Post\UserNotification::NOTIF_THREAD_COMMENT]);
|
||||
}
|
||||
|
||||
if (in_array('mention', $request['exclude_types'])) {
|
||||
$condition = DBA::mergeConditions($condition, ["(NOT `vid` IN (?) OR NOT `type` IN (?, ?, ?, ?, ?))",
|
||||
Verb::getID(Activity::POST), Post\UserNotification::NOTIF_EXPLICIT_TAGGED,
|
||||
Post\UserNotification::NOTIF_IMPLICIT_TAGGED, Post\UserNotification::NOTIF_DIRECT_COMMENT,
|
||||
Post\UserNotification::NOTIF_DIRECT_THREAD_COMMENT, Post\UserNotification::NOTIF_THREAD_COMMENT]);
|
||||
}
|
||||
|
||||
if (in_array('status', $request['exclude_types'])) {
|
||||
$condition = DBA::mergeConditions($condition, ["(NOT `vid` IN (?) OR NOT `type` IN (?))",
|
||||
Verb::getID(Activity::POST), Post\UserNotification::NOTIF_SHARED]);
|
||||
}
|
||||
|
||||
if (!empty($request['max_id'])) {
|
||||
|
|
@ -101,15 +126,20 @@ class Notifications extends BaseApi
|
|||
|
||||
$notifications = [];
|
||||
|
||||
$notify = DBA::select('notify', ['id'], $condition, $params);
|
||||
$notify = DBA::select('notification', ['id'], $condition, $params);
|
||||
while ($notification = DBA::fetch($notify)) {
|
||||
$notifications[] = DI::mstdnNotification()->createFromNotifyId($notification['id']);
|
||||
self::setBoundaries($notification['id']);
|
||||
$entry = DI::mstdnNotification()->createFromNotificationId($notification['id']);
|
||||
if (!empty($entry)) {
|
||||
$notifications[] = $entry;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($request['min_id'])) {
|
||||
array_reverse($notifications);
|
||||
}
|
||||
|
||||
self::setLinkHeader();
|
||||
System::jsonExit($notifications);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,10 +32,10 @@ class Clear extends BaseApi
|
|||
{
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_WRITE);
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
DBA::update('notify', ['seen' => true], ['uid' => $uid]);
|
||||
DBA::update('notification', ['seen' => true], ['uid' => $uid]);
|
||||
|
||||
System::jsonExit([]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,14 +33,14 @@ class Dismiss extends BaseApi
|
|||
{
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_WRITE);
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
DI::mstdnError()->UnprocessableEntity();
|
||||
}
|
||||
|
||||
DBA::update('notify', ['seen' => true], ['uid' => $uid, 'id' => $parameters['id']]);
|
||||
DBA::update('notification', ['seen' => true], ['uid' => $uid, 'id' => $parameters['id']]);
|
||||
|
||||
System::jsonExit([]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class Preferences extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
$user = User::getById($uid, ['language', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid']);
|
||||
|
|
@ -52,7 +52,7 @@ class Preferences extends BaseApi
|
|||
$sensitive = false;
|
||||
$language = $user['language'];
|
||||
$media = DI::pConfig()->get($uid, 'nsfw', 'disable') ? 'show_all' : 'default';
|
||||
$spoilers = DI::pConfig()->get($uid, 'system', 'disable_cw');
|
||||
$spoilers = (bool)DI::pConfig()->get($uid, 'system', 'disable_cw');
|
||||
|
||||
$preferences = new \Friendica\Object\Api\Mastodon\Preferences($visibility, $sensitive, $language, $media, $spoilers);
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class Search extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
$request = self::getRequest([
|
||||
|
|
@ -162,6 +162,7 @@ class Search extends BaseApi
|
|||
|
||||
$statuses = [];
|
||||
while ($item = Post::fetch($items)) {
|
||||
self::setBoundaries($item['uri-id']);
|
||||
$statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid);
|
||||
}
|
||||
DBA::close($items);
|
||||
|
|
@ -170,6 +171,7 @@ class Search extends BaseApi
|
|||
array_reverse($statuses);
|
||||
}
|
||||
|
||||
self::setLinkHeader();
|
||||
return $statuses;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,24 +43,25 @@ class Statuses extends BaseApi
|
|||
{
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_WRITE);
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
$data = self::getJsonPostData();
|
||||
|
||||
$status = $data['status'] ?? '';
|
||||
$media_ids = $data['media_ids'] ?? [];
|
||||
$in_reply_to_id = $data['in_reply_to_id'] ?? 0;
|
||||
$sensitive = $data['sensitive'] ?? false; // @todo Possibly trigger "nsfw" flag?
|
||||
$spoiler_text = $data['spoiler_text'] ?? '';
|
||||
$visibility = $data['visibility'] ?? '';
|
||||
$scheduled_at = $data['scheduled_at'] ?? ''; // Currently unsupported, but maybe in the future
|
||||
$language = $data['language'] ?? '';
|
||||
$request = self::getRequest([
|
||||
'status' => '', // Text content of the status. If media_ids is provided, this becomes optional. Attaching a poll is optional while status is provided.
|
||||
'media_ids' => [], // Array of Attachment ids to be attached as media. If provided, status becomes optional, and poll cannot be used.
|
||||
'poll' => [], // Poll data. If provided, media_ids cannot be used, and poll[expires_in] must be provided.
|
||||
'in_reply_to_id' => 0, // ID of the status being replied to, if status is a reply
|
||||
'sensitive' => false, // Mark status and attached media as sensitive?
|
||||
'spoiler_text' => '', // Text to be shown as a warning or subject before the actual content. Statuses are generally collapsed behind this field.
|
||||
'visibility' => '', // Visibility of the posted status. One of: "public", "unlisted", "private" or "direct".
|
||||
'scheduled_at' => '', // ISO 8601 Datetime at which to schedule a status. Providing this paramter will cause ScheduledStatus to be returned instead of Status. Must be at least 5 minutes in the future.
|
||||
'language' => '', // ISO 639 language code for this status.
|
||||
]);
|
||||
|
||||
$owner = User::getOwnerDataById($uid);
|
||||
|
||||
// The imput is defined as text. So we can use Markdown for some enhancements
|
||||
$body = Markdown::toBBCode($status);
|
||||
$body = Markdown::toBBCode($request['status']);
|
||||
|
||||
$body = BBCode::expandTags($body);
|
||||
|
||||
|
|
@ -69,7 +70,7 @@ class Statuses extends BaseApi
|
|||
$item['verb'] = Activity::POST;
|
||||
$item['contact-id'] = $owner['id'];
|
||||
$item['author-id'] = $item['owner-id'] = Contact::getPublicIdByUserId($uid);
|
||||
$item['title'] = $spoiler_text;
|
||||
$item['title'] = $request['spoiler_text'];
|
||||
$item['body'] = $body;
|
||||
|
||||
if (!empty(self::getCurrentApplication()['name'])) {
|
||||
|
|
@ -80,7 +81,7 @@ class Statuses extends BaseApi
|
|||
$item['app'] = 'API';
|
||||
}
|
||||
|
||||
switch ($visibility) {
|
||||
switch ($request['visibility']) {
|
||||
case 'public':
|
||||
$item['allow_cid'] = '';
|
||||
$item['allow_gid'] = '';
|
||||
|
|
@ -112,7 +113,7 @@ class Statuses extends BaseApi
|
|||
case 'direct':
|
||||
// Direct messages are currently unsupported
|
||||
DI::mstdnError()->InternalError('Direct messages are currently unsupported');
|
||||
break;
|
||||
break;
|
||||
default:
|
||||
$item['allow_cid'] = $owner['allow_cid'];
|
||||
$item['allow_gid'] = $owner['allow_gid'];
|
||||
|
|
@ -129,12 +130,12 @@ class Statuses extends BaseApi
|
|||
break;
|
||||
}
|
||||
|
||||
if (!empty($language)) {
|
||||
$item['language'] = json_encode([$language => 1]);
|
||||
if (!empty($request['language'])) {
|
||||
$item['language'] = json_encode([$request['language'] => 1]);
|
||||
}
|
||||
|
||||
if ($in_reply_to_id) {
|
||||
$parent = Post::selectFirst(['uri'], ['uri-id' => $in_reply_to_id, 'uid' => [0, $uid]]);
|
||||
if ($request['in_reply_to_id']) {
|
||||
$parent = Post::selectFirst(['uri'], ['uri-id' => $request['in_reply_to_id'], 'uid' => [0, $uid]]);
|
||||
$item['thr-parent'] = $parent['uri'];
|
||||
$item['gravity'] = GRAVITY_COMMENT;
|
||||
$item['object-type'] = Activity\ObjectType::COMMENT;
|
||||
|
|
@ -143,16 +144,16 @@ class Statuses extends BaseApi
|
|||
$item['object-type'] = Activity\ObjectType::NOTE;
|
||||
}
|
||||
|
||||
if (!empty($media_ids)) {
|
||||
if (!empty($request['media_ids'])) {
|
||||
$item['object-type'] = Activity\ObjectType::IMAGE;
|
||||
$item['post-type'] = Item::PT_IMAGE;
|
||||
$item['attachments'] = [];
|
||||
|
||||
foreach ($media_ids as $id) {
|
||||
foreach ($request['media_ids'] as $id) {
|
||||
$media = DBA::toArray(DBA::p("SELECT `resource-id`, `scale`, `type`, `desc`, `filename`, `datasize`, `width`, `height` FROM `photo`
|
||||
WHERE `resource-id` IN (SELECT `resource-id` FROM `photo` WHERE `id` = ?) AND `photo`.`uid` = ?
|
||||
ORDER BY `photo`.`width` DESC LIMIT 2", $id, $uid));
|
||||
|
||||
|
||||
if (empty($media)) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -162,7 +163,7 @@ class Statuses extends BaseApi
|
|||
$ressources[] = $media[0]['resource-id'];
|
||||
$phototypes = Images::supportedTypes();
|
||||
$ext = $phototypes[$media[0]['type']];
|
||||
|
||||
|
||||
$attachment = ['type' => Post\Media::IMAGE, 'mimetype' => $media[0]['type'],
|
||||
'url' => DI::baseUrl() . '/photo/' . $media[0]['resource-id'] . '-' . $media[0]['scale'] . '.' . $ext,
|
||||
'size' => $media[0]['datasize'],
|
||||
|
|
@ -170,7 +171,7 @@ class Statuses extends BaseApi
|
|||
'description' => $media[0]['desc'] ?? '',
|
||||
'width' => $media[0]['width'],
|
||||
'height' => $media[0]['height']];
|
||||
|
||||
|
||||
if (count($media) > 1) {
|
||||
$attachment['preview'] = DI::baseUrl() . '/photo/' . $media[1]['resource-id'] . '-' . $media[1]['scale'] . '.' . $ext;
|
||||
$attachment['preview-width'] = $media[1]['width'];
|
||||
|
|
@ -184,7 +185,7 @@ class Statuses extends BaseApi
|
|||
if (!empty($id)) {
|
||||
$item = Post::selectFirst(['uri-id'], ['id' => $id]);
|
||||
if (!empty($item['uri-id'])) {
|
||||
System::jsonExit(DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid));
|
||||
System::jsonExit(DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -193,7 +194,7 @@ class Statuses extends BaseApi
|
|||
|
||||
public static function delete(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
@ -218,10 +219,12 @@ class Statuses extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
DI::mstdnError()->UnprocessableEntity();
|
||||
}
|
||||
|
||||
System::jsonExit(DI::mstdnStatus()->createFromUriId($parameters['id'], self::getCurrentUserID()));
|
||||
System::jsonExit(DI::mstdnStatus()->createFromUriId($parameters['id'], $uid));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class Bookmark extends BaseApi
|
|||
{
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_WRITE);
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
|
|||
|
|
@ -44,6 +44,10 @@ class Context extends BaseApi
|
|||
DI::mstdnError()->UnprocessableEntity();
|
||||
}
|
||||
|
||||
$request = self::getRequest([
|
||||
'limit' => 40, // Maximum number of results to return. Defaults to 40.
|
||||
]);
|
||||
|
||||
$id = $parameters['id'];
|
||||
|
||||
$parent = Post::selectFirst(['parent-uri-id'], ['uri-id' => $id]);
|
||||
|
|
@ -54,8 +58,8 @@ class Context extends BaseApi
|
|||
$parents = [];
|
||||
$children = [];
|
||||
|
||||
$posts = Post::select(['uri-id', 'thr-parent-id'],
|
||||
['parent-uri-id' => $parent['parent-uri-id'], 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT]], [], false);
|
||||
$posts = Post::selectPosts(['uri-id', 'thr-parent-id'],
|
||||
['parent-uri-id' => $parent['parent-uri-id'], 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT]], []);
|
||||
while ($post = Post::fetch($posts)) {
|
||||
if ($post['uri-id'] == $post['thr-parent-id']) {
|
||||
continue;
|
||||
|
|
@ -68,24 +72,20 @@ class Context extends BaseApi
|
|||
|
||||
$statuses = ['ancestors' => [], 'descendants' => []];
|
||||
|
||||
$ancestors = [];
|
||||
foreach (self::getParents($id, $parents) as $ancestor) {
|
||||
$ancestors[$ancestor] = DI::mstdnStatus()->createFromUriId($ancestor, $uid);
|
||||
$ancestors = self::getParents($id, $parents);
|
||||
|
||||
asort($ancestors);
|
||||
|
||||
foreach (array_slice($ancestors, 0, $request['limit']) as $ancestor) {
|
||||
$statuses['ancestors'][] = DI::mstdnStatus()->createFromUriId($ancestor, $uid);;
|
||||
}
|
||||
|
||||
ksort($ancestors);
|
||||
foreach ($ancestors as $ancestor) {
|
||||
$statuses['ancestors'][] = $ancestor;
|
||||
}
|
||||
$descendants = self::getChildren($id, $children);
|
||||
|
||||
$descendants = [];
|
||||
foreach (self::getChildren($id, $children) as $descendant) {
|
||||
$descendants[] = DI::mstdnStatus()->createFromUriId($descendant, $uid);
|
||||
}
|
||||
asort($descendants);
|
||||
|
||||
ksort($descendants);
|
||||
foreach ($descendants as $descendant) {
|
||||
$statuses['descendants'][] = $descendant;
|
||||
foreach (array_slice($descendants, 0, $request['limit']) as $descendant) {
|
||||
$statuses['descendants'][] = DI::mstdnStatus()->createFromUriId($descendant, $uid);
|
||||
}
|
||||
|
||||
System::jsonExit($statuses);
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class Favourite extends BaseApi
|
|||
{
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_WRITE);
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class FavouritedBy extends BaseApi
|
|||
DI::mstdnError()->RecordNotFound();
|
||||
}
|
||||
|
||||
$activities = Post::select(['author-id'], ['thr-parent-id' => $id, 'gravity' => GRAVITY_ACTIVITY, 'verb' => Activity::LIKE], [], false);
|
||||
$activities = Post::selectPosts(['author-id'], ['thr-parent-id' => $id, 'gravity' => GRAVITY_ACTIVITY, 'verb' => Activity::LIKE]);
|
||||
|
||||
$accounts = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class Mute extends BaseApi
|
|||
{
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_WRITE);
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class Pin extends BaseApi
|
|||
{
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_WRITE);
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class Reblog extends BaseApi
|
|||
{
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_WRITE);
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class RebloggedBy extends BaseApi
|
|||
DI::mstdnError()->RecordNotFound();
|
||||
}
|
||||
|
||||
$activities = Post::select(['author-id'], ['thr-parent-id' => $id, 'gravity' => GRAVITY_ACTIVITY, 'verb' => Activity::ANNOUNCE], [], false);
|
||||
$activities = Post::selectPosts(['author-id'], ['thr-parent-id' => $id, 'gravity' => GRAVITY_ACTIVITY, 'verb' => Activity::ANNOUNCE]);
|
||||
|
||||
$accounts = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class Unbookmark extends BaseApi
|
|||
{
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_WRITE);
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class Unfavourite extends BaseApi
|
|||
{
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_WRITE);
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class Unmute extends BaseApi
|
|||
{
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_WRITE);
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class Unpin extends BaseApi
|
|||
{
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_WRITE);
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class Unreblog extends BaseApi
|
|||
{
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_WRITE);
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class Suggestions extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
$request = self::getRequest([
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class Direct extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
$request = self::getRequest([
|
||||
|
|
@ -48,22 +48,22 @@ class Direct extends BaseApi
|
|||
'limit' => 20, // Maximum number of results to return. Defaults to 20.
|
||||
]);
|
||||
|
||||
$params = ['order' => ['id' => true], 'limit' => $request['limit']];
|
||||
$params = ['order' => ['uri-id' => true], 'limit' => $request['limit']];
|
||||
|
||||
$condition = ['uid' => $uid];
|
||||
|
||||
if (!empty($request['max_id'])) {
|
||||
$condition = DBA::mergeConditions($condition, ["`id` < ?", $request['max_id']]);
|
||||
$condition = DBA::mergeConditions($condition, ["`uri-id` < ?", $request['max_id']]);
|
||||
}
|
||||
|
||||
if (!empty($request['since_id'])) {
|
||||
$condition = DBA::mergeConditions($condition, ["`id` > ?", $request['since_id']]);
|
||||
$condition = DBA::mergeConditions($condition, ["`uri-id` > ?", $request['since_id']]);
|
||||
}
|
||||
|
||||
if (!empty($request['min_id'])) {
|
||||
$condition = DBA::mergeConditions($condition, ["`id` > ?", $request['min_id']]);
|
||||
$condition = DBA::mergeConditions($condition, ["`uri-id` > ?", $request['min_id']]);
|
||||
|
||||
$params['order'] = ['id'];
|
||||
$params['order'] = ['uri-id'];
|
||||
}
|
||||
|
||||
$mails = DBA::select('mail', ['id'], $condition, $params);
|
||||
|
|
@ -71,6 +71,7 @@ class Direct extends BaseApi
|
|||
$statuses = [];
|
||||
|
||||
while ($mail = DBA::fetch($mails)) {
|
||||
self::setBoundaries($mail['uri-id']);
|
||||
$statuses[] = DI::mstdnStatus()->createFromMailId($mail['id']);
|
||||
}
|
||||
|
||||
|
|
@ -78,6 +79,7 @@ class Direct extends BaseApi
|
|||
array_reverse($statuses);
|
||||
}
|
||||
|
||||
self::setLinkHeader();
|
||||
System::jsonExit($statuses);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ class Home extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
$request = self::getRequest([
|
||||
|
|
@ -93,6 +93,7 @@ class Home extends BaseApi
|
|||
|
||||
$statuses = [];
|
||||
while ($item = Post::fetch($items)) {
|
||||
self::setBoundaries($item['uri-id']);
|
||||
$statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid);
|
||||
}
|
||||
DBA::close($items);
|
||||
|
|
@ -101,6 +102,7 @@ class Home extends BaseApi
|
|||
array_reverse($statuses);
|
||||
}
|
||||
|
||||
self::setLinkHeader();
|
||||
System::jsonExit($statuses);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ class ListTimeline extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
|
|
@ -98,6 +98,7 @@ class ListTimeline extends BaseApi
|
|||
|
||||
$statuses = [];
|
||||
while ($item = Post::fetch($items)) {
|
||||
self::setBoundaries($item['uri-id']);
|
||||
$statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid);
|
||||
}
|
||||
DBA::close($items);
|
||||
|
|
@ -106,6 +107,7 @@ class ListTimeline extends BaseApi
|
|||
array_reverse($statuses);
|
||||
}
|
||||
|
||||
self::setLinkHeader();
|
||||
System::jsonExit($statuses);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ class PublicTimeline extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
$request = self::getRequest([
|
||||
'local' => false, // Show only local statuses? Defaults to false.
|
||||
'remote' => false, // Show only remote statuses? Defaults to false.
|
||||
|
|
@ -56,7 +58,7 @@ class PublicTimeline extends BaseApi
|
|||
$params = ['order' => ['uri-id' => true], 'limit' => $request['limit']];
|
||||
|
||||
$condition = ['gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT], 'private' => Item::PUBLIC,
|
||||
'uid' => 0, 'network' => Protocol::FEDERATED];
|
||||
'network' => Protocol::FEDERATED, 'parent-author-blocked' => false, 'parent-author-hidden' => false];
|
||||
|
||||
if ($request['local']) {
|
||||
$condition = DBA::mergeConditions($condition, ["`uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `origin`)"]);
|
||||
|
|
@ -88,11 +90,17 @@ class PublicTimeline extends BaseApi
|
|||
$condition = DBA::mergeConditions($condition, ['gravity' => GRAVITY_PARENT]);
|
||||
}
|
||||
|
||||
$items = Post::selectForUser(0, ['uri-id', 'uid'], $condition, $params);
|
||||
if (!empty($uid)) {
|
||||
$condition = DBA::mergeConditions($condition,
|
||||
["NOT EXISTS (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `cid` = `parent-author-id` AND (`blocked` OR `ignored`))", $uid]);
|
||||
}
|
||||
|
||||
$items = Post::selectPostsForUser($uid, ['uri-id'], $condition, $params);
|
||||
|
||||
$statuses = [];
|
||||
while ($item = Post::fetch($items)) {
|
||||
$statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $item['uid']);
|
||||
self::setBoundaries($item['uri-id']);
|
||||
$statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid);
|
||||
}
|
||||
DBA::close($items);
|
||||
|
||||
|
|
@ -100,6 +108,7 @@ class PublicTimeline extends BaseApi
|
|||
array_reverse($statuses);
|
||||
}
|
||||
|
||||
self::setLinkHeader();
|
||||
System::jsonExit($statuses);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,13 +40,20 @@ class Tag extends BaseApi
|
|||
*/
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
self::login(self::SCOPE_READ);
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($parameters['hashtag'])) {
|
||||
DI::mstdnError()->UnprocessableEntity();
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Respect missing parameters
|
||||
* @see https://github.com/tootsuite/mastodon/blob/main/app/controllers/api/v1/timelines/tag_controller.rb
|
||||
*
|
||||
* There seem to be the parameters "any", "all", and "none".
|
||||
*/
|
||||
|
||||
$request = self::getRequest([
|
||||
'local' => false, // If true, return only local statuses. Defaults to false.
|
||||
'remote' => false, // Show only remote statuses? Defaults to false.
|
||||
|
|
@ -100,6 +107,7 @@ class Tag extends BaseApi
|
|||
|
||||
$statuses = [];
|
||||
while ($item = Post::fetch($items)) {
|
||||
self::setBoundaries($item['uri-id']);
|
||||
$statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid);
|
||||
}
|
||||
DBA::close($items);
|
||||
|
|
@ -108,6 +116,7 @@ class Tag extends BaseApi
|
|||
array_reverse($statuses);
|
||||
}
|
||||
|
||||
self::setLinkHeader();
|
||||
System::jsonExit($statuses);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,8 @@ class Trends extends BaseApi
|
|||
$tags = Tag::getGlobalTrendingHashtags(24, 20);
|
||||
foreach ($tags as $tag) {
|
||||
$tag['name'] = $tag['term'];
|
||||
$hashtag = new \Friendica\Object\Api\Mastodon\Tag(DI::baseUrl(), $tag);
|
||||
$history = [['day' => (string)time(), 'uses' => (string)$tag['score'], 'accounts' => (string)$tag['authors']]];
|
||||
$hashtag = new \Friendica\Object\Api\Mastodon\Tag(DI::baseUrl(), $tag, $history);
|
||||
$trending[] = $hashtag->toArray();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,9 +39,7 @@ abstract class ContactEndpoint extends BaseApi
|
|||
{
|
||||
parent::init($parameters);
|
||||
|
||||
if (!self::login(self::SCOPE_READ)) {
|
||||
throw new HTTPException\UnauthorizedException();
|
||||
}
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -54,7 +52,7 @@ abstract class ContactEndpoint extends BaseApi
|
|||
*/
|
||||
protected static function getUid(int $contact_id = null, string $screen_name = null)
|
||||
{
|
||||
$uid = self::$current_user_id;
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if ($contact_id || $screen_name) {
|
||||
// screen_name trumps user_id when both are provided
|
||||
|
|
@ -129,7 +127,7 @@ abstract class ContactEndpoint extends BaseApi
|
|||
protected static function ids($rel, int $uid, int $cursor = -1, int $count = self::DEFAULT_COUNT, bool $stringify_ids = false)
|
||||
{
|
||||
$hide_friends = false;
|
||||
if ($uid != self::$current_user_id) {
|
||||
if ($uid != self::getCurrentUserID()) {
|
||||
$profile = Profile::getByUID($uid);
|
||||
if (empty($profile)) {
|
||||
throw new HTTPException\NotFoundException(DI::l10n()->t('Profile not found'));
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class Attach extends BaseModule
|
|||
|
||||
// @TODO: Replace with parameter from router
|
||||
$item_id = intval($a->argv[1]);
|
||||
|
||||
|
||||
// Check for existence
|
||||
$item = MAttach::exists(['id' => $item_id]);
|
||||
if ($item === false) {
|
||||
|
|
|
|||
|
|
@ -24,12 +24,11 @@ namespace Friendica\Module;
|
|||
use Friendica\BaseModule;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Network;
|
||||
use Friendica\Security\BasicAuth;
|
||||
use Friendica\Security\OAuth;
|
||||
use Friendica\Util\HTTPInputData;
|
||||
|
||||
require_once __DIR__ . '/../../include/api.php';
|
||||
|
||||
|
|
@ -44,14 +43,16 @@ class BaseApi extends BaseModule
|
|||
* @var string json|xml|rss|atom
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
/**
|
||||
* @var bool|int
|
||||
*/
|
||||
protected static $current_user_id;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $current_token = [];
|
||||
protected static $boundaries = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $request = [];
|
||||
|
||||
public static function init(array $parameters = [])
|
||||
{
|
||||
|
|
@ -70,52 +71,44 @@ class BaseApi extends BaseModule
|
|||
|
||||
public static function delete(array $parameters = [])
|
||||
{
|
||||
if (!api_user()) {
|
||||
throw new HTTPException\UnauthorizedException(DI::l10n()->t('Permission denied.'));
|
||||
}
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
|
||||
$a = DI::app();
|
||||
|
||||
if (!empty($a->user['uid']) && $a->user['uid'] != api_user()) {
|
||||
if (!empty($a->user['uid']) && $a->user['uid'] != self::getCurrentUserID()) {
|
||||
throw new HTTPException\ForbiddenException(DI::l10n()->t('Permission denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public static function patch(array $parameters = [])
|
||||
{
|
||||
if (!api_user()) {
|
||||
throw new HTTPException\UnauthorizedException(DI::l10n()->t('Permission denied.'));
|
||||
}
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
|
||||
$a = DI::app();
|
||||
|
||||
if (!empty($a->user['uid']) && $a->user['uid'] != api_user()) {
|
||||
if (!empty($a->user['uid']) && $a->user['uid'] != self::getCurrentUserID()) {
|
||||
throw new HTTPException\ForbiddenException(DI::l10n()->t('Permission denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
if (!api_user()) {
|
||||
throw new HTTPException\UnauthorizedException(DI::l10n()->t('Permission denied.'));
|
||||
}
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
|
||||
$a = DI::app();
|
||||
|
||||
if (!empty($a->user['uid']) && $a->user['uid'] != api_user()) {
|
||||
if (!empty($a->user['uid']) && $a->user['uid'] != self::getCurrentUserID()) {
|
||||
throw new HTTPException\ForbiddenException(DI::l10n()->t('Permission denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public static function put(array $parameters = [])
|
||||
{
|
||||
if (!api_user()) {
|
||||
throw new HTTPException\UnauthorizedException(DI::l10n()->t('Permission denied.'));
|
||||
}
|
||||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
|
||||
$a = DI::app();
|
||||
|
||||
if (!empty($a->user['uid']) && $a->user['uid'] != api_user()) {
|
||||
if (!empty($a->user['uid']) && $a->user['uid'] != self::getCurrentUserID()) {
|
||||
throw new HTTPException\ForbiddenException(DI::l10n()->t('Permission denied.'));
|
||||
}
|
||||
}
|
||||
|
|
@ -129,7 +122,7 @@ class BaseApi extends BaseModule
|
|||
public static function unsupported(string $method = 'all')
|
||||
{
|
||||
$path = DI::args()->getQueryString();
|
||||
Logger::info('Unimplemented API call', ['method' => $method, 'path' => $path, 'agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', 'request' => $_REQUEST ?? []]);
|
||||
Logger::info('Unimplemented API call', ['method' => $method, 'path' => $path, 'agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', 'request' => HTTPInputData::process()]);
|
||||
$error = DI::l10n()->t('API endpoint %s %s is not implemented', strtoupper($method), $path);
|
||||
$error_description = DI::l10n()->t('The API endpoint is currently not implemented but might be in the future.');
|
||||
$errorobj = new \Friendica\Object\Api\Mastodon\Error($error, $error_description);
|
||||
|
|
@ -141,26 +134,35 @@ class BaseApi extends BaseModule
|
|||
*
|
||||
* @return array request data
|
||||
*/
|
||||
public static function getRequest(array $defaults) {
|
||||
public static function getRequest(array $defaults)
|
||||
{
|
||||
$httpinput = HTTPInputData::process();
|
||||
$input = array_merge($httpinput['variables'], $httpinput['files'], $_REQUEST);
|
||||
|
||||
self::$request = $input;
|
||||
self::$boundaries = [];
|
||||
|
||||
unset(self::$request['pagename']);
|
||||
|
||||
$request = [];
|
||||
|
||||
foreach ($defaults as $parameter => $defaultvalue) {
|
||||
if (is_string($defaultvalue)) {
|
||||
$request[$parameter] = $_REQUEST[$parameter] ?? $defaultvalue;
|
||||
$request[$parameter] = $input[$parameter] ?? $defaultvalue;
|
||||
} elseif (is_int($defaultvalue)) {
|
||||
$request[$parameter] = (int)($_REQUEST[$parameter] ?? $defaultvalue);
|
||||
$request[$parameter] = (int)($input[$parameter] ?? $defaultvalue);
|
||||
} elseif (is_float($defaultvalue)) {
|
||||
$request[$parameter] = (float)($_REQUEST[$parameter] ?? $defaultvalue);
|
||||
$request[$parameter] = (float)($input[$parameter] ?? $defaultvalue);
|
||||
} elseif (is_array($defaultvalue)) {
|
||||
$request[$parameter] = $_REQUEST[$parameter] ?? [];
|
||||
$request[$parameter] = $input[$parameter] ?? [];
|
||||
} elseif (is_bool($defaultvalue)) {
|
||||
$request[$parameter] = in_array(strtolower($_REQUEST[$parameter] ?? ''), ['true', '1']);
|
||||
$request[$parameter] = in_array(strtolower($input[$parameter] ?? ''), ['true', '1']);
|
||||
} else {
|
||||
Logger::notice('Unhandled default value type', ['parameter' => $parameter, 'type' => gettype($defaultvalue)]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($_REQUEST ?? [] as $parameter => $value) {
|
||||
foreach ($input ?? [] as $parameter => $value) {
|
||||
if ($parameter == 'pagename') {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -174,98 +176,68 @@ class BaseApi extends BaseModule
|
|||
}
|
||||
|
||||
/**
|
||||
* Get post data that is transmitted as JSON
|
||||
*
|
||||
* @return array request data
|
||||
* Set boundaries for the "link" header
|
||||
* @param array $boundaries
|
||||
* @param int $id
|
||||
* @return array
|
||||
*/
|
||||
public static function getJsonPostData()
|
||||
protected static function setBoundaries(int $id)
|
||||
{
|
||||
$postdata = Network::postdata();
|
||||
if (empty($postdata)) {
|
||||
return [];
|
||||
if (!isset(self::$boundaries['min'])) {
|
||||
self::$boundaries['min'] = $id;
|
||||
}
|
||||
|
||||
return json_decode($postdata, true);
|
||||
if (!isset(self::$boundaries['max'])) {
|
||||
self::$boundaries['max'] = $id;
|
||||
}
|
||||
|
||||
self::$boundaries['min'] = min(self::$boundaries['min'], $id);
|
||||
self::$boundaries['max'] = max(self::$boundaries['max'], $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get request data for put requests
|
||||
*
|
||||
* @return array request data
|
||||
* Set the "link" header with "next" and "prev" links
|
||||
* @return void
|
||||
*/
|
||||
public static function getPutData()
|
||||
protected static function setLinkHeader()
|
||||
{
|
||||
$rawdata = Network::postdata();
|
||||
if (empty($rawdata)) {
|
||||
return [];
|
||||
if (empty(self::$boundaries)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$putdata = [];
|
||||
$request = self::$request;
|
||||
|
||||
foreach (explode('&', $rawdata) as $value) {
|
||||
$data = explode('=', $value);
|
||||
if (count($data) == 2) {
|
||||
$putdata[$data[0]] = urldecode($data[1]);
|
||||
}
|
||||
}
|
||||
unset($request['min_id']);
|
||||
unset($request['max_id']);
|
||||
unset($request['since_id']);
|
||||
|
||||
return $putdata;
|
||||
$prev_request = $next_request = $request;
|
||||
|
||||
$prev_request['min_id'] = self::$boundaries['max'];
|
||||
$next_request['max_id'] = self::$boundaries['min'];
|
||||
|
||||
$command = DI::baseUrl() . '/' . DI::args()->getCommand();
|
||||
|
||||
$prev = $command . '?' . http_build_query($prev_request);
|
||||
$next = $command . '?' . http_build_query($next_request);
|
||||
|
||||
header('Link: <' . $next . '>; rel="next", <' . $prev . '>; rel="prev"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Log in user via OAuth1 or Simple HTTP Auth.
|
||||
*
|
||||
* Simple Auth allow username in form of <pre>user@server</pre>, ignoring server part
|
||||
*
|
||||
* @param string $scope the requested scope (read, write, follow)
|
||||
*
|
||||
* @return bool Was a user authenticated?
|
||||
* @throws HTTPException\ForbiddenException
|
||||
* @throws HTTPException\UnauthorizedException
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
* @hook 'authenticate'
|
||||
* array $addon_auth
|
||||
* 'username' => username from login form
|
||||
* 'password' => password from login form
|
||||
* 'authenticated' => return status,
|
||||
* 'user_record' => return authenticated user record
|
||||
*/
|
||||
protected static function login(string $scope)
|
||||
{
|
||||
if (empty(self::$current_user_id)) {
|
||||
self::$current_token = self::getTokenByBearer();
|
||||
if (!empty(self::$current_token['uid'])) {
|
||||
self::$current_user_id = self::$current_token['uid'];
|
||||
} else {
|
||||
self::$current_user_id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($scope) && !empty(self::$current_token)) {
|
||||
if (empty(self::$current_token[$scope])) {
|
||||
Logger::warning('The requested scope is not allowed', ['scope' => $scope, 'application' => self::$current_token]);
|
||||
DI::mstdnError()->Forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
if (empty(self::$current_user_id)) {
|
||||
// The execution stops here if no one is logged in
|
||||
api_login(DI::app());
|
||||
}
|
||||
|
||||
self::$current_user_id = api_user();
|
||||
|
||||
return (bool)self::$current_user_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current application
|
||||
* Get current application token
|
||||
*
|
||||
* @return array token
|
||||
*/
|
||||
protected static function getCurrentApplication()
|
||||
{
|
||||
return self::$current_token;
|
||||
$token = OAuth::getCurrentApplicationToken();
|
||||
|
||||
if (empty($token)) {
|
||||
$token = BasicAuth::getCurrentApplicationToken();
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -275,131 +247,39 @@ class BaseApi extends BaseModule
|
|||
*/
|
||||
protected static function getCurrentUserID()
|
||||
{
|
||||
if (empty(self::$current_user_id)) {
|
||||
self::$current_token = self::getTokenByBearer();
|
||||
if (!empty(self::$current_token['uid'])) {
|
||||
self::$current_user_id = self::$current_token['uid'];
|
||||
} else {
|
||||
self::$current_user_id = 0;
|
||||
}
|
||||
$uid = OAuth::getCurrentUserID();
|
||||
|
||||
if (empty($uid)) {
|
||||
$uid = BasicAuth::getCurrentUserID(false);
|
||||
}
|
||||
|
||||
if (empty(self::$current_user_id)) {
|
||||
// Fetch the user id if logged in - but don't fail if not
|
||||
api_login(DI::app(), false);
|
||||
|
||||
self::$current_user_id = api_user();
|
||||
}
|
||||
|
||||
return (int)self::$current_user_id;
|
||||
return (int)$uid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user token via the Bearer token
|
||||
* Check if the provided scope does exist.
|
||||
* halts execution on missing scope or when not logged in.
|
||||
*
|
||||
* @return array User Token
|
||||
* @param string $scope the requested scope (read, write, follow, push)
|
||||
*/
|
||||
private static function getTokenByBearer()
|
||||
public static function checkAllowedScope(string $scope)
|
||||
{
|
||||
$authorization = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
|
||||
$token = self::getCurrentApplication();
|
||||
|
||||
if (substr($authorization, 0, 7) != 'Bearer ') {
|
||||
return [];
|
||||
if (empty($token)) {
|
||||
Logger::notice('Empty application token');
|
||||
DI::mstdnError()->Forbidden();
|
||||
}
|
||||
|
||||
$bearer = trim(substr($authorization, 7));
|
||||
$condition = ['access_token' => $bearer];
|
||||
$token = DBA::selectFirst('application-view', ['uid', 'id', 'name', 'website', 'created_at', 'read', 'write', 'follow', 'push'], $condition);
|
||||
if (!DBA::isResult($token)) {
|
||||
Logger::warning('Token not found', $condition);
|
||||
return [];
|
||||
}
|
||||
Logger::debug('Token found', $token);
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the application record via the proved request header fields
|
||||
*
|
||||
* @param string $client_id
|
||||
* @param string $client_secret
|
||||
* @param string $redirect_uri
|
||||
* @return array application record
|
||||
*/
|
||||
public static function getApplication(string $client_id, string $client_secret, string $redirect_uri)
|
||||
{
|
||||
$condition = ['client_id' => $client_id];
|
||||
if (!empty($client_secret)) {
|
||||
$condition['client_secret'] = $client_secret;
|
||||
}
|
||||
if (!empty($redirect_uri)) {
|
||||
$condition['redirect_uri'] = $redirect_uri;
|
||||
if (!isset($token[$scope])) {
|
||||
Logger::warning('The requested scope does not exist', ['scope' => $scope, 'application' => $token]);
|
||||
DI::mstdnError()->Forbidden();
|
||||
}
|
||||
|
||||
$application = DBA::selectFirst('application', [], $condition);
|
||||
if (!DBA::isResult($application)) {
|
||||
Logger::warning('Application not found', $condition);
|
||||
return [];
|
||||
if (empty($token[$scope])) {
|
||||
Logger::warning('The requested scope is not allowed', ['scope' => $scope, 'application' => $token]);
|
||||
DI::mstdnError()->Forbidden();
|
||||
}
|
||||
return $application;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an token for the application and user exists
|
||||
*
|
||||
* @param array $application
|
||||
* @param integer $uid
|
||||
* @return boolean
|
||||
*/
|
||||
public static function existsTokenForUser(array $application, int $uid)
|
||||
{
|
||||
return DBA::exists('application-token', ['application-id' => $application['id'], 'uid' => $uid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the token for the given application and user
|
||||
*
|
||||
* @param array $application
|
||||
* @param integer $uid
|
||||
* @return array application record
|
||||
*/
|
||||
public static function getTokenForUser(array $application, int $uid)
|
||||
{
|
||||
return DBA::selectFirst('application-token', [], ['application-id' => $application['id'], 'uid' => $uid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and fetch an token for the application and user
|
||||
*
|
||||
* @param array $application
|
||||
* @param integer $uid
|
||||
* @param string $scope
|
||||
* @return array application record
|
||||
*/
|
||||
public static function createTokenForUser(array $application, int $uid, string $scope)
|
||||
{
|
||||
$code = bin2hex(random_bytes(32));
|
||||
$access_token = bin2hex(random_bytes(32));
|
||||
|
||||
$fields = ['application-id' => $application['id'], 'uid' => $uid, 'code' => $code, 'access_token' => $access_token, 'scopes' => $scope,
|
||||
'read' => (stripos($scope, self::SCOPE_READ) !== false),
|
||||
'write' => (stripos($scope, self::SCOPE_WRITE) !== false),
|
||||
'follow' => (stripos($scope, self::SCOPE_FOLLOW) !== false),
|
||||
'push' => (stripos($scope, self::SCOPE_PUSH) !== false),
|
||||
'created_at' => DateTimeFormat::utcNow(DateTimeFormat::MYSQL)];
|
||||
|
||||
foreach ([self::SCOPE_READ, self::SCOPE_WRITE, self::SCOPE_WRITE, self::SCOPE_PUSH] as $scope) {
|
||||
if ($fields[$scope] && !$application[$scope]) {
|
||||
Logger::warning('Requested token scope is not allowed for the application', ['token' => $fields, 'application' => $application]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!DBA::insert('application-token', $fields, Database::INSERT_UPDATE)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return DBA::selectFirst('application-token', [], ['application-id' => $application['id'], 'uid' => $uid]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -20,8 +20,10 @@ use Friendica\Model\Item;
|
|||
use Friendica\Model\Post;
|
||||
use Friendica\Model\Profile;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Model\Verb;
|
||||
use Friendica\Module\Contact as ModuleContact;
|
||||
use Friendica\Module\Security\Login;
|
||||
use Friendica\Protocol\Activity;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
||||
class Network extends BaseModule
|
||||
|
|
@ -388,7 +390,9 @@ class Network extends BaseModule
|
|||
if (self::$groupId) {
|
||||
$conditionStrings = DBA::mergeConditions($conditionStrings, ["`contact-id` IN (SELECT `contact-id` FROM `group_member` WHERE `gid` = ?)", self::$groupId]);
|
||||
} elseif (self::$forumContactId) {
|
||||
$conditionFields['contact-id'] = self::$forumContactId;
|
||||
$conditionStrings = DBA::mergeConditions($conditionStrings,
|
||||
["((`contact-id` = ?) OR EXISTS(SELECT `uri-id` FROM `post-user-view` WHERE `post-user-view`.`parent-uri-id` = `network-thread-view`.`uri-id` AND (`contact-id` = ? AND `gravity` = ? AND `vid` = ? AND `uid` = ?)))",
|
||||
self::$forumContactId, self::$forumContactId, GRAVITY_ACTIVITY, Verb::getID(Activity::ANNOUNCE), local_user()]);
|
||||
}
|
||||
|
||||
// Currently only the order modes "received" and "commented" are in use
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ use Friendica\Core\Session;
|
|||
use Friendica\Database\DBA;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Util\Strings;
|
||||
|
||||
/**
|
||||
* Performs an activity (like, dislike, announce, attendyes, attendno, attendmaybe)
|
||||
|
|
@ -90,7 +89,7 @@ class Activity extends BaseModule
|
|||
{
|
||||
$fields = ['uri-id', 'body', 'title', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink'];
|
||||
$item = Post::selectFirst($fields, ['id' => $itemId, 'private' => [Item::PUBLIC, Item::UNLISTED]]);
|
||||
if (!DBA::isResult($item) || ($item['body'] == '')) {
|
||||
if (!DBA::isResult($item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ use Friendica\Core\Protocol;
|
|||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Profile;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\User;
|
||||
|
||||
/**
|
||||
|
|
@ -50,20 +50,20 @@ class NoScrape extends BaseModule
|
|||
System::jsonError(403, 'Authentication required');
|
||||
}
|
||||
|
||||
Profile::load($a, $which);
|
||||
$profile = User::getOwnerDataByNick($which);
|
||||
|
||||
if (empty($a->profile['uid'])) {
|
||||
if (empty($profile['uid'])) {
|
||||
System::jsonError(404, 'Profile not found');
|
||||
}
|
||||
|
||||
$json_info = [
|
||||
'addr' => $a->profile['addr'],
|
||||
'addr' => $profile['addr'],
|
||||
'nick' => $which,
|
||||
'guid' => $a->profile['guid'],
|
||||
'key' => $a->profile['upubkey'],
|
||||
'guid' => $profile['guid'],
|
||||
'key' => $profile['upubkey'],
|
||||
'homepage' => DI::baseUrl() . "/profile/{$which}",
|
||||
'comm' => ($a->profile['account-type'] == User::ACCOUNT_TYPE_COMMUNITY),
|
||||
'account-type' => $a->profile['account-type'],
|
||||
'comm' => ($profile['account-type'] == User::ACCOUNT_TYPE_COMMUNITY),
|
||||
'account-type' => $profile['account-type'],
|
||||
];
|
||||
|
||||
$dfrn_pages = ['request', 'confirm', 'notify', 'poll'];
|
||||
|
|
@ -71,30 +71,28 @@ class NoScrape extends BaseModule
|
|||
$json_info["dfrn-{$dfrn}"] = DI::baseUrl() . "/dfrn_{$dfrn}/{$which}";
|
||||
}
|
||||
|
||||
if (!$a->profile['net-publish']) {
|
||||
if (!$profile['net-publish']) {
|
||||
$json_info['hide'] = true;
|
||||
System::jsonExit($json_info);
|
||||
}
|
||||
|
||||
$keywords = $a->profile['pub_keywords'] ?? '';
|
||||
$keywords = $profile['pub_keywords'] ?? '';
|
||||
$keywords = str_replace(['#', ',', ' ', ',,'], ['', ' ', ',', ','], $keywords);
|
||||
$keywords = explode(',', $keywords);
|
||||
|
||||
$contactPhoto = DBA::selectFirst('contact', ['photo'], ['self' => true, 'uid' => $a->profile['uid']]);
|
||||
|
||||
$json_info['fn'] = $a->profile['name'];
|
||||
$json_info['photo'] = $contactPhoto["photo"];
|
||||
$json_info['fn'] = $profile['name'];
|
||||
$json_info['photo'] = Contact::getAvatarUrlForUrl($profile['url'], $profile['uid']);
|
||||
$json_info['tags'] = $keywords;
|
||||
$json_info['language'] = $a->profile['language'];
|
||||
$json_info['language'] = $profile['language'];
|
||||
|
||||
if (!empty($a->profile['last-item'])) {
|
||||
$json_info['updated'] = date("c", strtotime($a->profile['last-item']));
|
||||
if (!empty($profile['last-item'])) {
|
||||
$json_info['updated'] = date("c", strtotime($profile['last-item']));
|
||||
}
|
||||
|
||||
if (!($a->profile['hide-friends'] ?? false)) {
|
||||
if (!($profile['hide-friends'] ?? false)) {
|
||||
$json_info['contacts'] = DBA::count('contact',
|
||||
[
|
||||
'uid' => $a->profile['uid'],
|
||||
'uid' => $profile['uid'],
|
||||
'self' => 0,
|
||||
'blocked' => 0,
|
||||
'pending' => 0,
|
||||
|
|
@ -106,13 +104,13 @@ class NoScrape extends BaseModule
|
|||
|
||||
// We display the last activity (post or login), reduced to year and week number
|
||||
$last_active = 0;
|
||||
$condition = ['uid' => $a->profile['uid'], 'self' => true];
|
||||
$condition = ['uid' => $profile['uid'], 'self' => true];
|
||||
$contact = DBA::selectFirst('contact', ['last-item'], $condition);
|
||||
if (DBA::isResult($contact)) {
|
||||
$last_active = strtotime($contact['last-item']);
|
||||
}
|
||||
|
||||
$condition = ['uid' => $a->profile['uid']];
|
||||
$condition = ['uid' => $profile['uid']];
|
||||
$user = DBA::selectFirst('user', ['login_date'], $condition);
|
||||
if (DBA::isResult($user)) {
|
||||
if ($last_active < strtotime($user['login_date'])) {
|
||||
|
|
@ -124,8 +122,8 @@ class NoScrape extends BaseModule
|
|||
//These are optional fields.
|
||||
$profile_fields = ['about', 'locality', 'region', 'postal-code', 'country-name'];
|
||||
foreach ($profile_fields as $field) {
|
||||
if (!empty($a->profile[$field])) {
|
||||
$json_info["$field"] = $a->profile[$field];
|
||||
if (!empty($profile[$field])) {
|
||||
$json_info["$field"] = $profile[$field];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ namespace Friendica\Module\OAuth;
|
|||
use Friendica\Core\Logger;
|
||||
use Friendica\DI;
|
||||
use Friendica\Module\BaseApi;
|
||||
use Friendica\Security\OAuth;
|
||||
|
||||
/**
|
||||
* @see https://docs.joinmastodon.org/spec/oauth/
|
||||
|
|
@ -31,6 +32,8 @@ use Friendica\Module\BaseApi;
|
|||
*/
|
||||
class Authorize extends BaseApi
|
||||
{
|
||||
private static $oauth_code = '';
|
||||
|
||||
/**
|
||||
* @param array $parameters
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
|
|
@ -38,11 +41,12 @@ class Authorize extends BaseApi
|
|||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
$request = self::getRequest([
|
||||
'response_type' => '',
|
||||
'client_id' => '',
|
||||
'force_login' => '', // Forces the user to re-login, which is necessary for authorizing with multiple accounts from the same instance.
|
||||
'response_type' => '', // Should be set equal to "code".
|
||||
'client_id' => '', // Client ID, obtained during app registration.
|
||||
'client_secret' => '', // Isn't normally provided. We will use it if present.
|
||||
'redirect_uri' => '',
|
||||
'scope' => 'read',
|
||||
'redirect_uri' => '', // Set a URI to redirect the user to. If this parameter is set to "urn:ietf:wg:oauth:2.0:oob" then the authorization code will be shown instead. Must match one of the redirect URIs declared during app registration.
|
||||
'scope' => 'read', // List of requested OAuth scopes, separated by spaces (or by pluses, if using query parameters). Must be a subset of scopes declared during app registration. If not provided, defaults to "read".
|
||||
'state' => '',
|
||||
]);
|
||||
|
||||
|
|
@ -56,16 +60,16 @@ class Authorize extends BaseApi
|
|||
DI::mstdnError()->UnprocessableEntity(DI::l10n()->t('Incomplete request data'));
|
||||
}
|
||||
|
||||
$application = self::getApplication($request['client_id'], $request['client_secret'], $request['redirect_uri']);
|
||||
$application = OAuth::getApplication($request['client_id'], $request['client_secret'], $request['redirect_uri']);
|
||||
if (empty($application)) {
|
||||
DI::mstdnError()->UnprocessableEntity();
|
||||
}
|
||||
|
||||
// @todo Compare the application scope and requested scope
|
||||
|
||||
$request = $_REQUEST;
|
||||
unset($request['pagename']);
|
||||
$redirect = 'oauth/authorize?' . http_build_query($request);
|
||||
$redirect_request = $_REQUEST;
|
||||
unset($redirect_request['pagename']);
|
||||
$redirect = 'oauth/authorize?' . http_build_query($redirect_request);
|
||||
|
||||
$uid = local_user();
|
||||
if (empty($uid)) {
|
||||
|
|
@ -75,18 +79,31 @@ class Authorize extends BaseApi
|
|||
Logger::info('Already logged in user', ['uid' => $uid]);
|
||||
}
|
||||
|
||||
if (!self::existsTokenForUser($application, $uid) && !DI::session()->get('oauth_acknowledge')) {
|
||||
if (!OAuth::existsTokenForUser($application, $uid) && !DI::session()->get('oauth_acknowledge')) {
|
||||
Logger::info('Redirect to acknowledge');
|
||||
DI::app()->redirect('oauth/acknowledge?' . http_build_query(['return_path' => $redirect, 'application' => $application['name']]));
|
||||
}
|
||||
|
||||
DI::session()->remove('oauth_acknowledge');
|
||||
|
||||
$token = self::createTokenForUser($application, $uid, $request['scope']);
|
||||
$token = OAuth::createTokenForUser($application, $uid, $request['scope']);
|
||||
if (!$token) {
|
||||
DI::mstdnError()->UnprocessableEntity();
|
||||
}
|
||||
|
||||
DI::app()->redirect($application['redirect_uri'] . (strpos($application['redirect_uri'], '?') ? '&' : '?') . http_build_query(['code' => $token['code'], 'state' => $request['state']]));
|
||||
if ($application['redirect_uri'] != 'urn:ietf:wg:oauth:2.0:oob') {
|
||||
DI::app()->redirect($application['redirect_uri'] . (strpos($application['redirect_uri'], '?') ? '&' : '?') . http_build_query(['code' => $token['code'], 'state' => $request['state']]));
|
||||
}
|
||||
|
||||
self::$oauth_code = $token['code'];
|
||||
}
|
||||
|
||||
public static function content(array $parameters = [])
|
||||
{
|
||||
if (empty(self::$oauth_code)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return DI::l10n()->t('Please copy the following authentication code into your application and close this window: %s', self::$oauth_code);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@
|
|||
|
||||
namespace Friendica\Module\OAuth;
|
||||
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Module\BaseApi;
|
||||
|
||||
/**
|
||||
|
|
@ -30,6 +34,20 @@ class Revoke extends BaseApi
|
|||
{
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
self::unsupported('post');
|
||||
$request = self::getRequest([
|
||||
'client_id' => '', // Client ID, obtained during app registration
|
||||
'client_secret' => '', // Client secret, obtained during app registration
|
||||
'token' => '', // The previously obtained token, to be invalidated
|
||||
]);
|
||||
|
||||
$condition = ['client_id' => $request['client_id'], 'client_secret' => $request['client_secret'], 'access_token' => $request['token']];
|
||||
$token = DBA::selectFirst('application-view', ['id'], $condition);
|
||||
if (empty($token['id'])) {
|
||||
Logger::warning('Token not found', $condition);
|
||||
DI::mstdnError()->Unauthorized();
|
||||
}
|
||||
|
||||
DBA::delete('application-token', ['application-id' => $token['id']]);
|
||||
System::jsonExit([]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ use Friendica\Core\System;
|
|||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Module\BaseApi;
|
||||
use Friendica\Security\OAuth;
|
||||
|
||||
/**
|
||||
* @see https://docs.joinmastodon.org/spec/oauth/
|
||||
|
|
@ -36,16 +37,23 @@ class Token extends BaseApi
|
|||
public static function post(array $parameters = [])
|
||||
{
|
||||
$request = self::getRequest([
|
||||
'grant_type' => '',
|
||||
'code' => '',
|
||||
'redirect_uri' => '',
|
||||
'client_id' => '',
|
||||
'client_secret' => '',
|
||||
'client_id' => '', // Client ID, obtained during app registration
|
||||
'client_secret' => '', // Client secret, obtained during app registration
|
||||
'redirect_uri' => '', // Set a URI to redirect the user to. If this parameter is set to "urn:ietf:wg:oauth:2.0:oob" then the token will be shown instead. Must match one of the redirect URIs declared during app registration.
|
||||
'scope' => 'read', // List of requested OAuth scopes, separated by spaces. Must be a subset of scopes declared during app registration. If not provided, defaults to "read".
|
||||
'code' => '', // A user authorization code, obtained via /oauth/authorize
|
||||
'grant_type' => '', // Set equal to "authorization_code" if code is provided in order to gain user-level access. Otherwise, set equal to "client_credentials" to obtain app-level access only.
|
||||
]);
|
||||
|
||||
// AndStatus transmits the client data in the AUTHORIZATION header field, see https://github.com/andstatus/andstatus/issues/530
|
||||
if (empty($request['client_id']) && !empty($_SERVER['HTTP_AUTHORIZATION']) && (substr($_SERVER['HTTP_AUTHORIZATION'], 0, 6) == 'Basic ')) {
|
||||
$datapair = explode(':', base64_decode(trim(substr($_SERVER['HTTP_AUTHORIZATION'], 6))));
|
||||
$authorization = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
|
||||
if (empty($authorization)) {
|
||||
// workaround for HTTP-auth in CGI mode
|
||||
$authorization = $_SERVER['REDIRECT_REMOTE_USER'] ?? '';
|
||||
}
|
||||
|
||||
if (empty($request['client_id']) && substr($authorization, 0, 6) == 'Basic ') {
|
||||
$datapair = explode(':', base64_decode(trim(substr($authorization, 6))));
|
||||
if (count($datapair) == 2) {
|
||||
$request['client_id'] = $datapair[0];
|
||||
$request['client_secret'] = $datapair[1];
|
||||
|
|
@ -57,7 +65,7 @@ class Token extends BaseApi
|
|||
DI::mstdnError()->UnprocessableEntity(DI::l10n()->t('Incomplete request data'));
|
||||
}
|
||||
|
||||
$application = self::getApplication($request['client_id'], $request['client_secret'], $request['redirect_uri']);
|
||||
$application = OAuth::getApplication($request['client_id'], $request['client_secret'], $request['redirect_uri']);
|
||||
if (empty($application)) {
|
||||
DI::mstdnError()->UnprocessableEntity();
|
||||
}
|
||||
|
|
@ -65,7 +73,7 @@ class Token extends BaseApi
|
|||
if ($request['grant_type'] == 'client_credentials') {
|
||||
// the "client_credentials" are used as a token for the application itself.
|
||||
// see https://aaronparecki.com/oauth-2-simplified/#client-credentials
|
||||
$token = self::createTokenForUser($application, 0, '');
|
||||
$token = OAuth::createTokenForUser($application, 0, '');
|
||||
} elseif ($request['grant_type'] == 'authorization_code') {
|
||||
// For security reasons only allow freshly created tokens
|
||||
$condition = ["`redirect_uri` = ? AND `id` = ? AND `code` = ? AND `created_at` > UTC_TIMESTAMP() - INTERVAL ? MINUTE",
|
||||
|
|
|
|||
|
|
@ -27,8 +27,14 @@ use Friendica\Database\DBA;
|
|||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Photo as MPhoto;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Model\Profile;
|
||||
use Friendica\Model\Storage\ExternalResource;
|
||||
use Friendica\Model\Storage\SystemResource;
|
||||
use Friendica\Util\Proxy;
|
||||
use Friendica\Object\Image;
|
||||
use Friendica\Util\Images;
|
||||
use Friendica\Util\Network;
|
||||
|
||||
/**
|
||||
* Photo Module
|
||||
|
|
@ -44,11 +50,6 @@ class Photo extends BaseModule
|
|||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
$totalstamp = microtime(true);
|
||||
$a = DI::app();
|
||||
// @TODO: Replace with parameter from router
|
||||
if ($a->argc <= 1 || $a->argc > 4) {
|
||||
throw new \Friendica\Network\HTTPException\BadRequestException();
|
||||
}
|
||||
|
||||
if (isset($_SERVER["HTTP_IF_MODIFIED_SINCE"])) {
|
||||
header("HTTP/1.1 304 Not Modified");
|
||||
|
|
@ -66,33 +67,34 @@ class Photo extends BaseModule
|
|||
exit;
|
||||
}
|
||||
|
||||
Profile::addVisitorCookieForHTTPSigner();
|
||||
|
||||
$customsize = 0;
|
||||
$square_resize = true;
|
||||
$photo = false;
|
||||
$scale = null;
|
||||
// @TODO: Replace with parameter from router
|
||||
$stamp = microtime(true);
|
||||
switch($a->argc) {
|
||||
case 4:
|
||||
$customsize = intval($a->argv[2]);
|
||||
$uid = MPhoto::stripExtension($a->argv[3]);
|
||||
$photo = self::getAvatar($uid, $a->argv[1]);
|
||||
break;
|
||||
case 3:
|
||||
$uid = MPhoto::stripExtension($a->argv[2]);
|
||||
$photo = self::getAvatar($uid, $a->argv[1]);
|
||||
break;
|
||||
case 2:
|
||||
$photoid = MPhoto::stripExtension($a->argv[1]);
|
||||
$scale = 0;
|
||||
if (substr($photoid, -2, 1) == "-") {
|
||||
$scale = intval(substr($photoid, -1, 1));
|
||||
$photoid = substr($photoid, 0, -2);
|
||||
}
|
||||
$photo = MPhoto::getPhoto($photoid, $scale);
|
||||
if ($photo === false) {
|
||||
throw new \Friendica\Network\HTTPException\NotFoundException(DI::l10n()->t('The Photo with id %s is not available.', $photoid));
|
||||
}
|
||||
break;
|
||||
if (!empty($parameters['customsize'])) {
|
||||
$customsize = intval($parameters['customsize']);
|
||||
$uid = MPhoto::stripExtension($parameters['name']);
|
||||
$photo = self::getAvatar($uid, $parameters['type'], $customsize);
|
||||
$square_resize = !in_array($parameters['type'], ['media', 'preview']);
|
||||
} elseif (!empty($parameters['type'])) {
|
||||
$uid = MPhoto::stripExtension($parameters['name']);
|
||||
$photo = self::getAvatar($uid, $parameters['type'], Proxy::PIXEL_SMALL);
|
||||
} elseif (!empty($parameters['name'])) {
|
||||
$photoid = MPhoto::stripExtension($parameters['name']);
|
||||
$scale = 0;
|
||||
if (substr($photoid, -2, 1) == "-") {
|
||||
$scale = intval(substr($photoid, -1, 1));
|
||||
$photoid = substr($photoid, 0, -2);
|
||||
}
|
||||
$photo = MPhoto::getPhoto($photoid, $scale);
|
||||
if ($photo === false) {
|
||||
throw new \Friendica\Network\HTTPException\NotFoundException(DI::l10n()->t('The Photo with id %s is not available.', $photoid));
|
||||
}
|
||||
} else {
|
||||
throw new \Friendica\Network\HTTPException\BadRequestException();
|
||||
}
|
||||
$fetch = microtime(true) - $stamp;
|
||||
|
||||
|
|
@ -104,6 +106,15 @@ class Photo extends BaseModule
|
|||
|
||||
$stamp = microtime(true);
|
||||
$imgdata = MPhoto::getImageDataForPhoto($photo);
|
||||
|
||||
// The mimetype for an external or system resource can only be known reliably after it had been fetched
|
||||
if (in_array($photo['backend-class'], [ExternalResource::NAME, SystemResource::NAME])) {
|
||||
$mimetype = Images::getMimeTypeByData($imgdata);
|
||||
if (!empty($mimetype)) {
|
||||
$photo['type'] = $mimetype;
|
||||
}
|
||||
}
|
||||
|
||||
$data = microtime(true) - $stamp;
|
||||
|
||||
if (empty($imgdata)) {
|
||||
|
|
@ -112,10 +123,14 @@ class Photo extends BaseModule
|
|||
}
|
||||
|
||||
// if customsize is set and image is not a gif, resize it
|
||||
if ($photo['type'] !== "image/gif" && $customsize > 0 && $customsize < 501) {
|
||||
if ($photo['type'] !== "image/gif" && $customsize > 0 && $customsize <= Proxy::PIXEL_THUMB && $square_resize) {
|
||||
$img = new Image($imgdata, $photo['type']);
|
||||
$img->scaleToSquare($customsize);
|
||||
$imgdata = $img->asString();
|
||||
} elseif ($photo['type'] !== "image/gif" && $customsize > 0) {
|
||||
$img = new Image($imgdata, $photo['type']);
|
||||
$img->scaleDown($customsize);
|
||||
$imgdata = $img->asString();
|
||||
}
|
||||
|
||||
if (function_exists("header_remove")) {
|
||||
|
|
@ -157,9 +172,83 @@ class Photo extends BaseModule
|
|||
exit();
|
||||
}
|
||||
|
||||
private static function getAvatar($uid, $type="avatar")
|
||||
private static function getAvatar($uid, $type="avatar", $customsize)
|
||||
{
|
||||
switch($type) {
|
||||
case "preview":
|
||||
$media = DBA::selectFirst('post-media', ['preview', 'url', 'type', 'uri-id'], ['id' => $uid]);
|
||||
if (empty($media)) {
|
||||
return false;
|
||||
}
|
||||
$url = $media['preview'];
|
||||
|
||||
if (empty($url) && ($media['type'] == Post\Media::IMAGE)) {
|
||||
$url = $media['url'];
|
||||
}
|
||||
|
||||
if (empty($url)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Network::isLocalLink($url) && preg_match('|.*?/photo/(.*[a-fA-F0-9])\-(.*[0-9])\..*[\w]|', $url, $matches)) {
|
||||
return MPhoto::getPhoto($matches[1], $matches[2]);
|
||||
}
|
||||
|
||||
return MPhoto::createPhotoForExternalResource($url, (int)local_user());
|
||||
case "media":
|
||||
$media = DBA::selectFirst('post-media', ['url', 'uri-id'], ['id' => $uid, 'type' => Post\Media::IMAGE]);
|
||||
if (empty($media)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Network::isLocalLink($media['url']) && preg_match('|.*?/photo/(.*[a-fA-F0-9])\-(.*[0-9])\..*[\w]|', $media['url'], $matches)) {
|
||||
return MPhoto::getPhoto($matches[1], $matches[2]);
|
||||
}
|
||||
|
||||
return MPhoto::createPhotoForExternalResource($media['url'], (int)local_user());
|
||||
case "contact":
|
||||
$contact = Contact::getById($uid, ['uid', 'url', 'avatar', 'photo', 'xmpp', 'addr']);
|
||||
if (empty($contact)) {
|
||||
return false;
|
||||
}
|
||||
If (($contact['uid'] != 0) && empty($contact['photo']) && empty($contact['avatar'])) {
|
||||
$contact = Contact::getByURL($contact['url'], false, ['avatar', 'photo', 'xmpp', 'addr']);
|
||||
}
|
||||
if (!empty($contact['photo']) && !empty($contact['avatar'])) {
|
||||
// Fetch photo directly
|
||||
$resourceid = MPhoto::ridFromURI($contact['photo']);
|
||||
if (!empty($resourceid)) {
|
||||
$photo = MPhoto::selectFirst([], ['resource-id' => $resourceid], ['order' => ['scale']]);
|
||||
if (!empty($photo)) {
|
||||
return $photo;
|
||||
}
|
||||
}
|
||||
// We continue with the avatar link when the photo link is invalid
|
||||
$url = $contact['avatar'];
|
||||
} elseif (!empty($contact['avatar'])) {
|
||||
$url = $contact['avatar'];
|
||||
} elseif ($customsize <= Proxy::PIXEL_MICRO) {
|
||||
$url = Contact::getDefaultAvatar($contact, Proxy::SIZE_MICRO);
|
||||
} elseif ($customsize <= Proxy::PIXEL_THUMB) {
|
||||
$url = Contact::getDefaultAvatar($contact, Proxy::SIZE_THUMB);
|
||||
} else {
|
||||
$url = Contact::getDefaultAvatar($contact, Proxy::SIZE_SMALL);
|
||||
}
|
||||
return MPhoto::createPhotoForExternalResource($url);
|
||||
case "header":
|
||||
$contact = Contact::getById($uid, ['uid', 'url', 'header']);
|
||||
if (empty($contact)) {
|
||||
return false;
|
||||
}
|
||||
If (($contact['uid'] != 0) && empty($contact['header'])) {
|
||||
$contact = Contact::getByURL($contact['url'], false, ['header']);
|
||||
}
|
||||
if (!empty($contact['header'])) {
|
||||
$url = $contact['header'];
|
||||
} else {
|
||||
$url = DI::baseUrl() . '/images/blank.png';
|
||||
}
|
||||
return MPhoto::createPhotoForExternalResource($url);
|
||||
case "profile":
|
||||
case "custom":
|
||||
$scale = 4;
|
||||
|
|
@ -188,8 +277,13 @@ class Photo extends BaseModule
|
|||
default:
|
||||
$default = Contact::getDefaultAvatar($contact, Proxy::SIZE_THUMB);
|
||||
}
|
||||
|
||||
$photo = MPhoto::createPhotoForSystemResource($default);
|
||||
|
||||
$parts = parse_url($default);
|
||||
if (!empty($parts['scheme']) || !empty($parts['host'])) {
|
||||
$photo = MPhoto::createPhotoForExternalResource($default);
|
||||
} else {
|
||||
$photo = MPhoto::createPhotoForSystemResource($default);
|
||||
}
|
||||
}
|
||||
return $photo;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ namespace Friendica\Module;
|
|||
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Photo;
|
||||
use Friendica\Object\Image;
|
||||
|
|
@ -45,7 +46,7 @@ class Proxy extends BaseModule
|
|||
* Sets application instance and checks if /proxy/ path is writable.
|
||||
*
|
||||
*/
|
||||
public static function init(array $parameters = [])
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
// Set application instance here
|
||||
$a = DI::app();
|
||||
|
|
@ -89,6 +90,11 @@ class Proxy extends BaseModule
|
|||
throw new \Friendica\Network\HTTPException\BadRequestException();
|
||||
}
|
||||
|
||||
if (!local_user()) {
|
||||
Logger::info('Redirecting not logged in user to original address', ['url' => $request['url']]);
|
||||
System::externalRedirect($request['url']);
|
||||
}
|
||||
|
||||
// Webserver already tried direct cache...
|
||||
|
||||
// Try to use filecache;
|
||||
|
|
@ -181,7 +187,7 @@ class Proxy extends BaseModule
|
|||
private static function getRequestInfo()
|
||||
{
|
||||
$a = DI::app();
|
||||
$size = 1024;
|
||||
$size = ProxyUtils::PIXEL_LARGE;
|
||||
$sizetype = '';
|
||||
|
||||
// Look for filename in the arguments
|
||||
|
|
@ -203,23 +209,23 @@ class Proxy extends BaseModule
|
|||
|
||||
// thumb, small, medium and large.
|
||||
if (substr($url, -6) == ':micro') {
|
||||
$size = 48;
|
||||
$size = ProxyUtils::PIXEL_MICRO;
|
||||
$sizetype = ':micro';
|
||||
$url = substr($url, 0, -6);
|
||||
} elseif (substr($url, -6) == ':thumb') {
|
||||
$size = 80;
|
||||
$size = ProxyUtils::PIXEL_THUMB;
|
||||
$sizetype = ':thumb';
|
||||
$url = substr($url, 0, -6);
|
||||
} elseif (substr($url, -6) == ':small') {
|
||||
$size = 300;
|
||||
$size = ProxyUtils::PIXEL_SMALL;
|
||||
$url = substr($url, 0, -6);
|
||||
$sizetype = ':small';
|
||||
} elseif (substr($url, -7) == ':medium') {
|
||||
$size = 600;
|
||||
$size = ProxyUtils::PIXEL_MEDIUM;
|
||||
$url = substr($url, 0, -7);
|
||||
$sizetype = ':medium';
|
||||
} elseif (substr($url, -6) == ':large') {
|
||||
$size = 1024;
|
||||
$size = ProxyUtils::PIXEL_LARGE;
|
||||
$url = substr($url, 0, -6);
|
||||
$sizetype = ':large';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -248,10 +248,6 @@ class Register extends BaseModule
|
|||
DI::baseUrl()->redirect('register/');
|
||||
}
|
||||
|
||||
|
||||
// Overwriting the "tar pit" field with the real one
|
||||
$arr['email'] = $arr['field1'];
|
||||
|
||||
if ($additional_account) {
|
||||
$user = DBA::selectFirst('user', ['email'], ['uid' => local_user()]);
|
||||
if (!DBA::isResult($user)) {
|
||||
|
|
@ -264,6 +260,9 @@ class Register extends BaseModule
|
|||
|
||||
$arr['password1'] = $arr['confirm'] = $arr['parent_password'];
|
||||
$arr['repeat'] = $arr['email'] = $user['email'];
|
||||
} else {
|
||||
// Overwriting the "tar pit" field with the real one
|
||||
$arr['email'] = $arr['field1'];
|
||||
}
|
||||
|
||||
if ($arr['email'] != $arr['repeat']) {
|
||||
|
|
|
|||
|
|
@ -170,8 +170,10 @@ class Index extends BaseSearch
|
|||
}
|
||||
|
||||
if (!empty($uriids)) {
|
||||
$params = ['order' => ['id' => true], 'group_by' => ['uri-id']];
|
||||
$items = Post::toArray(Post::selectForUser(local_user(), Item::DISPLAY_FIELDLIST, ['uri-id' => $uriids], $params));
|
||||
$condition = ["(`uid` = ? OR (`uid` = ? AND NOT `global`))", 0, local_user()];
|
||||
$condition = DBA::mergeConditions($condition, ['uri-id' => $uriids]);
|
||||
$params = ['order' => ['id' => true]];
|
||||
$items = Post::toArray(Post::selectForUser(local_user(), Item::DISPLAY_FIELDLIST, $condition, $params));
|
||||
}
|
||||
|
||||
if (empty($items)) {
|
||||
|
|
|
|||
|
|
@ -86,8 +86,6 @@ class Index extends BaseSettings
|
|||
return;
|
||||
}
|
||||
|
||||
$namechanged = $profile['name'] != $name;
|
||||
|
||||
$about = Strings::escapeTags(trim($_POST['about']));
|
||||
$address = Strings::escapeTags(trim($_POST['address']));
|
||||
$locality = Strings::escapeTags(trim($_POST['locality']));
|
||||
|
|
@ -114,8 +112,7 @@ class Index extends BaseSettings
|
|||
|
||||
DI::profileField()->saveCollection($profileFields);
|
||||
|
||||
$result = DBA::update(
|
||||
'profile',
|
||||
$result = Profile::update(
|
||||
[
|
||||
'name' => $name,
|
||||
'about' => $about,
|
||||
|
|
@ -130,26 +127,13 @@ class Index extends BaseSettings
|
|||
'pub_keywords' => $pub_keywords,
|
||||
'prv_keywords' => $prv_keywords,
|
||||
],
|
||||
['uid' => local_user()]
|
||||
local_user()
|
||||
);
|
||||
|
||||
if (!$result) {
|
||||
notice(DI::l10n()->t('Profile couldn\'t be updated.'));
|
||||
return;
|
||||
}
|
||||
|
||||
if ($namechanged) {
|
||||
DBA::update('user', ['username' => $name], ['uid' => local_user()]);
|
||||
}
|
||||
|
||||
Contact::updateSelfFromUserID(local_user());
|
||||
|
||||
// Update global directory in background
|
||||
if (Session::get('my_url') && strlen(DI::config()->get('system', 'directory'))) {
|
||||
Worker::add(PRIORITY_LOW, 'Directory', Session::get('my_url'));
|
||||
}
|
||||
|
||||
Worker::add(PRIORITY_LOW, 'ProfileUpdate', local_user());
|
||||
}
|
||||
|
||||
public static function content(array $parameters = [])
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ use Friendica\Database\DBA;
|
|||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Photo;
|
||||
use Friendica\Model\Profile;
|
||||
use Friendica\Module\BaseSettings;
|
||||
use Friendica\Network\HTTPException;
|
||||
|
||||
|
|
@ -137,12 +138,9 @@ class Crop extends BaseSettings
|
|||
Contact::updateSelfFromUserID(local_user(), true);
|
||||
|
||||
info(DI::l10n()->t('Shift-reload the page or clear browser cache if the new photo does not display immediately.'));
|
||||
// Update global directory in background
|
||||
if ($path && strlen(DI::config()->get('system', 'directory'))) {
|
||||
Worker::add(PRIORITY_LOW, 'Directory', DI::baseUrl()->get() . '/' . $path);
|
||||
}
|
||||
|
||||
Worker::add(PRIORITY_LOW, 'ProfileUpdate', local_user());
|
||||
// Update global directory in background
|
||||
Profile::publishUpdate(local_user());
|
||||
} else {
|
||||
notice(DI::l10n()->t('Unable to process image'));
|
||||
}
|
||||
|
|
@ -183,9 +181,7 @@ class Crop extends BaseSettings
|
|||
Contact::updateSelfFromUserID(local_user(), true);
|
||||
|
||||
// Update global directory in background
|
||||
if (Session::get('my_url') && strlen(DI::config()->get('system', 'directory'))) {
|
||||
Worker::add(PRIORITY_LOW, 'Directory', Session::get('my_url'));
|
||||
}
|
||||
Profile::publishUpdate(local_user());
|
||||
|
||||
info(DI::l10n()->t('Profile picture successfully updated.'));
|
||||
|
||||
|
|
|
|||
|
|
@ -35,13 +35,7 @@ class XSocialRelay extends BaseModule
|
|||
{
|
||||
$config = DI::config();
|
||||
|
||||
$subscribe = $config->get('system', 'relay_subscribe', false);
|
||||
|
||||
if ($subscribe) {
|
||||
$scope = $config->get('system', 'relay_scope', SR_SCOPE_ALL);
|
||||
} else {
|
||||
$scope = SR_SCOPE_NONE;
|
||||
}
|
||||
$scope = $config->get('system', 'relay_scope');
|
||||
|
||||
$systemTags = [];
|
||||
$userTags = [];
|
||||
|
|
@ -63,7 +57,7 @@ class XSocialRelay extends BaseModule
|
|||
$tagList = array_unique(array_merge($systemTags, $userTags));
|
||||
|
||||
$relay = [
|
||||
'subscribe' => $subscribe,
|
||||
'subscribe' => ($scope != SR_SCOPE_NONE),
|
||||
'scope' => $scope,
|
||||
'tags' => $tagList,
|
||||
'protocols' => [
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ use Friendica\BaseModule;
|
|||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Photo;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Protocol\ActivityNamespace;
|
||||
|
|
@ -183,7 +183,7 @@ class Xrd extends BaseModule
|
|||
[
|
||||
'rel' => 'http://webfinger.net/rel/avatar',
|
||||
'type' => $avatar['type'],
|
||||
'href' => $owner['photo'],
|
||||
'href' => Contact::getAvatarUrlForUrl($owner['url'], $owner['uid']),
|
||||
],
|
||||
[
|
||||
'rel' => 'http://joindiaspora.com/seed_location',
|
||||
|
|
@ -239,7 +239,7 @@ class Xrd extends BaseModule
|
|||
'$hcard_url' => $baseURL . '/hcard/' . $owner['nickname'],
|
||||
'$atom' => $owner['poll'],
|
||||
'$poco_url' => $owner['poco'],
|
||||
'$photo' => $owner['photo'],
|
||||
'$photo' => Contact::getAvatarUrlForUrl($owner['url'], $owner['uid']),
|
||||
'$type' => $avatar['type'],
|
||||
'$salmon' => $baseURL . '/salmon/' . $owner['nickname'],
|
||||
'$salmen' => $baseURL . '/salmon/' . $owner['nickname'] . '/mention',
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ class HTTPRequest implements IHTTPRequest
|
|||
*
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public function get(string $url, array $opts = [], int &$redirects = 0)
|
||||
public function get(string $url, array $opts = [], &$redirects = 0)
|
||||
{
|
||||
$stamp1 = microtime(true);
|
||||
|
||||
|
|
@ -222,7 +222,7 @@ class HTTPRequest implements IHTTPRequest
|
|||
*
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public function post(string $url, $params, array $headers = [], int $timeout = 0, int &$redirects = 0)
|
||||
public function post(string $url, $params, array $headers = [], int $timeout = 0, &$redirects = 0)
|
||||
{
|
||||
$stamp1 = microtime(true);
|
||||
|
||||
|
|
@ -447,7 +447,7 @@ class HTTPRequest implements IHTTPRequest
|
|||
*
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public function fetch(string $url, int $timeout = 0, string $accept_content = '', string $cookiejar = '', int &$redirects = 0)
|
||||
public function fetch(string $url, int $timeout = 0, string $accept_content = '', string $cookiejar = '', &$redirects = 0)
|
||||
{
|
||||
$ret = $this->fetchFull($url, $timeout, $accept_content, $cookiejar, $redirects);
|
||||
|
||||
|
|
@ -461,7 +461,7 @@ class HTTPRequest implements IHTTPRequest
|
|||
*
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public function fetchFull(string $url, int $timeout = 0, string $accept_content = '', string $cookiejar = '', int &$redirects = 0)
|
||||
public function fetchFull(string $url, int $timeout = 0, string $accept_content = '', string $cookiejar = '', &$redirects = 0)
|
||||
{
|
||||
return $this->get(
|
||||
$url,
|
||||
|
|
|
|||
|
|
@ -87,8 +87,8 @@ class Probe
|
|||
*/
|
||||
private static function rearrangeData($data)
|
||||
{
|
||||
$fields = ["name", "nick", "guid", "url", "addr", "alias", "photo", "account-type",
|
||||
"community", "keywords", "location", "about", "hide",
|
||||
$fields = ["name", "nick", "guid", "url", "addr", "alias", "photo", "header",
|
||||
"account-type", "community", "keywords", "location", "about", "hide",
|
||||
"batch", "notify", "poll", "request", "confirm", "subscribe", "poco",
|
||||
"following", "followers", "inbox", "outbox", "sharedinbox",
|
||||
"priority", "network", "pubkey", "manually-approve", "baseurl", "gsid"];
|
||||
|
|
@ -1016,18 +1016,18 @@ class Probe
|
|||
$curlResult = DI::httpRequest()->get($noscrape_url);
|
||||
if ($curlResult->isTimeout()) {
|
||||
self::$istimeout = true;
|
||||
return [];
|
||||
return $data;
|
||||
}
|
||||
$content = $curlResult->getBody();
|
||||
if (!$content) {
|
||||
Logger::info('Empty body', ['url' => $noscrape_url]);
|
||||
return [];
|
||||
return $data;
|
||||
}
|
||||
|
||||
$json = json_decode($content, true);
|
||||
if (!is_array($json)) {
|
||||
Logger::info('No json data', ['url' => $noscrape_url]);
|
||||
return [];
|
||||
return $data;
|
||||
}
|
||||
|
||||
if (!empty($json["fn"])) {
|
||||
|
|
@ -2222,8 +2222,9 @@ class Probe
|
|||
|
||||
$data = ['name' => $profile['name'], 'nick' => $profile['nick'], 'guid' => $approfile['diaspora:guid'] ?? '',
|
||||
'url' => $profile['url'], 'addr' => $profile['addr'], 'alias' => $profile['alias'],
|
||||
'photo' => $profile['photo'], 'account-type' => $profile['contact-type'],
|
||||
'community' => ($profile['contact-type'] == User::ACCOUNT_TYPE_COMMUNITY),
|
||||
'photo' => Contact::getAvatarUrlForId($profile['id'], $profile['updated']),
|
||||
'header' => $profile['header'] ? Contact::getHeaderUrlForId($profile['id'], $profile['updated']) : '',
|
||||
'account-type' => $profile['contact-type'], 'community' => ($profile['contact-type'] == User::ACCOUNT_TYPE_COMMUNITY),
|
||||
'keywords' => $profile['keywords'], 'location' => $profile['location'], 'about' => $profile['about'],
|
||||
'hide' => !$profile['net-publish'], 'batch' => '', 'notify' => $profile['notify'],
|
||||
'poll' => $profile['poll'], 'request' => $profile['request'], 'confirm' => $profile['confirm'],
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ use Friendica\Content\Text\BBCode;
|
|||
use Friendica\Database\DBA;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Proxy;
|
||||
|
||||
/**
|
||||
* Class Account
|
||||
|
|
@ -108,15 +109,14 @@ class Account extends BaseDataTransferObject
|
|||
$userContactCreated = $userContact['created'] ?? DBA::NULL_DATETIME;
|
||||
|
||||
$created = $userContactCreated < $publicContactCreated && ($userContactCreated != DBA::NULL_DATETIME) ? $userContactCreated : $publicContactCreated;
|
||||
$this->created_at = DateTimeFormat::utc($created, DateTimeFormat::ATOM);
|
||||
$this->created_at = DateTimeFormat::utc($created, DateTimeFormat::JSON);
|
||||
|
||||
$this->note = BBCode::convert($publicContact['about'], false);
|
||||
$this->url = $publicContact['url'];
|
||||
$this->avatar = $userContact['avatar'] ?? $publicContact['avatar'];
|
||||
$this->avatar_static = $userContact['avatar'] ?? $publicContact['avatar'];
|
||||
// No header picture in Friendica
|
||||
$this->header = '';
|
||||
$this->header_static = '';
|
||||
$this->avatar = Contact::getAvatarUrlForId($userContact['id'] ?? 0 ?: $publicContact['id'], Proxy::SIZE_SMALL, $userContact['updated'] ?? '' ?: $publicContact['updated']);
|
||||
$this->avatar_static = $this->avatar;
|
||||
$this->header = Contact::getHeaderUrlForId($userContact['id'] ?? 0 ?: $publicContact['id'], '', $userContact['updated'] ?? '' ?: $publicContact['updated']);
|
||||
$this->header_static = $this->header;
|
||||
$this->followers_count = $apcontact['followers_count'] ?? 0;
|
||||
$this->following_count = $apcontact['following_count'] ?? 0;
|
||||
$this->statuses_count = $apcontact['statuses_count'] ?? 0;
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class Application extends BaseDataTransferObject
|
|||
protected $client_id;
|
||||
/** @var string */
|
||||
protected $client_secret;
|
||||
/** @var int */
|
||||
/** @var string */
|
||||
protected $id;
|
||||
/** @var string */
|
||||
protected $name;
|
||||
|
|
@ -53,7 +53,7 @@ class Application extends BaseDataTransferObject
|
|||
{
|
||||
$this->client_id = $client_id;
|
||||
$this->client_secret = $client_secret;
|
||||
$this->id = $id;
|
||||
$this->id = (string)$id;
|
||||
$this->name = $name;
|
||||
$this->redirect_uri = $redirect_uri;
|
||||
$this->website = $website;
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class Conversation extends BaseDataTransferObject
|
|||
|
||||
public function __construct(string $id, array $accounts, bool $unread, \Friendica\Object\Api\Mastodon\Status $last_status)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->id = (string)$id;
|
||||
$this->accounts = $accounts;
|
||||
$this->unread = $unread;
|
||||
$this->last_status = $last_status;
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ class Error extends BaseDataTransferObject
|
|||
*
|
||||
* @param string $error
|
||||
* @param string error_description
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public function __construct(string $error, string $error_description)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ class Instance extends BaseDataTransferObject
|
|||
/** @var string */
|
||||
protected $title;
|
||||
/** @var string */
|
||||
protected $short_description;
|
||||
/** @var string */
|
||||
protected $description;
|
||||
/** @var string */
|
||||
protected $email;
|
||||
|
|
@ -58,8 +60,12 @@ class Instance extends BaseDataTransferObject
|
|||
protected $registrations;
|
||||
/** @var bool */
|
||||
protected $approval_required;
|
||||
/** @var bool */
|
||||
protected $invites_enabled;
|
||||
/** @var Account|null */
|
||||
protected $contact_account = null;
|
||||
/** @var array */
|
||||
protected $rules = [];
|
||||
|
||||
/**
|
||||
* Creates an instance record
|
||||
|
|
@ -77,7 +83,7 @@ class Instance extends BaseDataTransferObject
|
|||
$instance = new Instance();
|
||||
$instance->uri = $baseUrl->get();
|
||||
$instance->title = DI::config()->get('config', 'sitename');
|
||||
$instance->description = DI::config()->get('config', 'info');
|
||||
$instance->short_description = $instance->description = DI::config()->get('config', 'info');
|
||||
$instance->email = DI::config()->get('config', 'admin_email');
|
||||
$instance->version = FRIENDICA_VERSION;
|
||||
$instance->urls = null; // Not supported
|
||||
|
|
@ -87,6 +93,7 @@ class Instance extends BaseDataTransferObject
|
|||
$instance->max_toot_chars = (int)DI::config()->get('config', 'api_import_size', DI::config()->get('config', 'max_import_size'));
|
||||
$instance->registrations = ($register_policy != Register::CLOSED);
|
||||
$instance->approval_required = ($register_policy == Register::APPROVE);
|
||||
$instance->invites_enabled = false;
|
||||
$instance->contact_account = [];
|
||||
|
||||
if (!empty(DI::config()->get('config', 'admin_email'))) {
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ class Mention extends BaseDataTransferObject
|
|||
*/
|
||||
public function __construct(BaseURL $baseUrl, array $tag, array $contact)
|
||||
{
|
||||
$this->id = $contact['id'] ?? 0;
|
||||
$this->id = (string)($contact['id'] ?? 0);
|
||||
$this->username = $tag['name'];
|
||||
$this->url = $tag['url'];
|
||||
|
||||
|
|
@ -59,6 +59,8 @@ class Mention extends BaseDataTransferObject
|
|||
strpos($contact['url'], $baseUrl->get() . '/') === 0 ?
|
||||
$contact['nick'] :
|
||||
$contact['addr'];
|
||||
|
||||
$this->username = $contact['nick'];
|
||||
} else {
|
||||
$this->acct = '';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@
|
|||
|
||||
namespace Friendica\Object\Api\Mastodon;
|
||||
|
||||
use Exception;
|
||||
use Friendica\BaseDataTransferObject;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
||||
/**
|
||||
|
|
@ -45,14 +47,13 @@ class Notification extends BaseDataTransferObject
|
|||
/**
|
||||
* Creates a notification record
|
||||
*
|
||||
* @param array $item
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws HttpException\InternalServerErrorException|Exception
|
||||
*/
|
||||
public function __construct(int $id, string $type, string $created_at, Account $account = null, Status $status = null)
|
||||
{
|
||||
$this->id = (string)$id;
|
||||
$this->type = $type;
|
||||
$this->created_at = DateTimeFormat::utc($created_at, DateTimeFormat::ATOM);
|
||||
$this->created_at = DateTimeFormat::utc($created_at, DateTimeFormat::JSON);
|
||||
$this->account = $account->toArray();
|
||||
|
||||
if (!empty($status)) {
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ class Relationship extends BaseDataTransferObject
|
|||
*/
|
||||
public function __construct(int $contactId, array $contactRecord = [], bool $blocked = false, bool $muted = false)
|
||||
{
|
||||
$this->id = $contactId;
|
||||
$this->id = (string)$contactId;
|
||||
$this->following = false;
|
||||
$this->requested = false;
|
||||
$this->endorsed = false;
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ class Status extends BaseDataTransferObject
|
|||
public function __construct(array $item, Account $account, Counts $counts, UserAttributes $userAttributes, bool $sensitive, Application $application, array $mentions, array $tags, Card $card, array $attachments, array $reblog)
|
||||
{
|
||||
$this->id = (string)$item['uri-id'];
|
||||
$this->created_at = DateTimeFormat::utc($item['created'], DateTimeFormat::ATOM);
|
||||
$this->created_at = DateTimeFormat::utc($item['created'], DateTimeFormat::JSON);
|
||||
|
||||
if ($item['gravity'] == GRAVITY_COMMENT) {
|
||||
$this->in_reply_to_id = (string)$item['thr-parent-id'];
|
||||
|
|
@ -114,7 +114,12 @@ class Status extends BaseDataTransferObject
|
|||
$this->visibility = $visibility[$item['private']];
|
||||
|
||||
$languages = json_decode($item['language'], true);
|
||||
$this->language = is_array($languages) ? array_key_first($languages) : null;
|
||||
if (is_array($languages)) {
|
||||
reset($languages);
|
||||
$this->language = key($languages);
|
||||
} else {
|
||||
$this->language = null;
|
||||
}
|
||||
|
||||
$this->uri = $item['uri'];
|
||||
$this->url = $item['plink'] ?? null;
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ class Tag extends BaseDataTransferObject
|
|||
protected $name;
|
||||
/** @var string */
|
||||
protected $url = null;
|
||||
/** @var array */
|
||||
protected $history = [];
|
||||
|
||||
/**
|
||||
* Creates a hashtag record from an tag-view record.
|
||||
|
|
@ -43,9 +45,10 @@ class Tag extends BaseDataTransferObject
|
|||
* @param array $tag tag-view record
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public function __construct(BaseURL $baseUrl, array $tag)
|
||||
public function __construct(BaseURL $baseUrl, array $tag, array $history = [])
|
||||
{
|
||||
$this->name = strtolower($tag['name']);
|
||||
$this->url = $baseUrl . '/search?tag=' . urlencode($this->name);
|
||||
$this->name = strtolower($tag['name']);
|
||||
$this->url = $baseUrl . '/search?tag=' . urlencode($this->name);
|
||||
$this->history = $history;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,12 +22,11 @@
|
|||
namespace Friendica\Object\Api\Mastodon;
|
||||
|
||||
use Friendica\BaseDataTransferObject;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
||||
/**
|
||||
* Class Error
|
||||
* Class Token
|
||||
*
|
||||
* @see https://docs.joinmastodon.org/entities/error
|
||||
* @see https://docs.joinmastodon.org/entities/token/
|
||||
*/
|
||||
class Token extends BaseDataTransferObject
|
||||
{
|
||||
|
|
@ -37,7 +36,7 @@ class Token extends BaseDataTransferObject
|
|||
protected $token_type;
|
||||
/** @var string */
|
||||
protected $scope;
|
||||
/** @var string (Datetime) */
|
||||
/** @var int (timestamp) */
|
||||
protected $created_at;
|
||||
|
||||
/**
|
||||
|
|
@ -53,6 +52,6 @@ class Token extends BaseDataTransferObject
|
|||
$this->access_token = $access_token;
|
||||
$this->token_type = $token_type;
|
||||
$this->scope = $scope;
|
||||
$this->created_at = DateTimeFormat::utc($created_at, DateTimeFormat::ATOM);
|
||||
$this->created_at = strtotime($created_at);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ use Friendica\Model\User;
|
|||
use Friendica\Protocol\Activity;
|
||||
use Friendica\Util\Crypto;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Proxy;
|
||||
use Friendica\Util\Strings;
|
||||
use Friendica\Util\Temporal;
|
||||
|
||||
|
|
@ -68,7 +69,6 @@ class Post
|
|||
private $thread = null;
|
||||
private $redirect_url = null;
|
||||
private $owner_url = '';
|
||||
private $owner_photo = '';
|
||||
private $owner_name = '';
|
||||
private $wall_to_wall = false;
|
||||
private $threaded = false;
|
||||
|
|
@ -401,9 +401,13 @@ class Post
|
|||
}
|
||||
|
||||
// Fetching of Diaspora posts doesn't always work. There are issues with reshares and possibly comments
|
||||
if (($item['network'] != Protocol::DIASPORA) && empty($comment) && !empty(Session::get('remote_comment'))) {
|
||||
if (!local_user() && ($item['network'] != Protocol::DIASPORA) && !empty(Session::get('remote_comment'))) {
|
||||
$remote_comment = [DI::l10n()->t('Comment this item on your system'), DI::l10n()->t('Remote comment'),
|
||||
str_replace('{uri}', urlencode($item['uri']), Session::get('remote_comment'))];
|
||||
|
||||
// Ensure to either display the remote comment or the local activities
|
||||
$buttons = [];
|
||||
$comment_html = '';
|
||||
} else {
|
||||
$remote_comment = '';
|
||||
}
|
||||
|
|
@ -455,7 +459,7 @@ class Post
|
|||
'profile_url' => $profile_link,
|
||||
'name' => $profile_name,
|
||||
'item_photo_menu_html' => item_photo_menu($item),
|
||||
'thumb' => DI::baseUrl()->remove($item['author-avatar']),
|
||||
'thumb' => DI::baseUrl()->remove(Contact::getAvatarUrlForUrl($item['author-link'], $item['uid'], Proxy::SIZE_THUMB)),
|
||||
'osparkle' => $osparkle,
|
||||
'sparkle' => $sparkle,
|
||||
'title' => $title,
|
||||
|
|
@ -469,7 +473,7 @@ class Post
|
|||
'shiny' => $shiny,
|
||||
'owner_self' => $item['author-link'] == Session::get('my_url'),
|
||||
'owner_url' => $this->getOwnerUrl(),
|
||||
'owner_photo' => DI::baseUrl()->remove($item['owner-avatar']),
|
||||
'owner_photo' => DI::baseUrl()->remove(Contact::getAvatarUrlForUrl($item['owner-link'], $item['uid'], Proxy::SIZE_THUMB)),
|
||||
'owner_name' => $this->getOwnerName(),
|
||||
'plink' => Item::getPlink($item),
|
||||
'edpost' => $edpost,
|
||||
|
|
@ -1002,7 +1006,6 @@ class Post
|
|||
// Put this person as the wall owner of the wall-to-wall notice.
|
||||
|
||||
$this->owner_url = Contact::magicLinkByContact($a->page_contact);
|
||||
$this->owner_photo = $a->page_contact['thumb'];
|
||||
$this->owner_name = $a->page_contact['name'];
|
||||
$this->wall_to_wall = true;
|
||||
} elseif ($this->getDataValue('owner-link')) {
|
||||
|
|
@ -1020,7 +1023,6 @@ class Post
|
|||
// But it could be somebody else with the same name. It just isn't highly likely.
|
||||
|
||||
|
||||
$this->owner_photo = $this->getDataValue('owner-avatar');
|
||||
$this->owner_name = $this->getDataValue('owner-name');
|
||||
$this->wall_to_wall = true;
|
||||
|
||||
|
|
@ -1036,7 +1038,6 @@ class Post
|
|||
if (!$this->wall_to_wall) {
|
||||
$this->setTemplate('wall');
|
||||
$this->owner_url = '';
|
||||
$this->owner_photo = '';
|
||||
$this->owner_name = '';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,9 +65,11 @@ class ActivityPub
|
|||
'dfrn' => 'http://purl.org/macgirvin/dfrn/1.0/',
|
||||
'diaspora' => 'https://diasporafoundation.org/ns/',
|
||||
'litepub' => 'http://litepub.social/ns#',
|
||||
'toot' => 'http://joinmastodon.org/ns#',
|
||||
'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
|
||||
'sensitive' => 'as:sensitive', 'Hashtag' => 'as:Hashtag',
|
||||
'directMessage' => 'litepub:directMessage']];
|
||||
'directMessage' => 'litepub:directMessage',
|
||||
'discoverable' => 'toot:discoverable']];
|
||||
const ACCOUNT_TYPES = ['Person', 'Organization', 'Service', 'Group', 'Application', 'Tombstone'];
|
||||
/**
|
||||
* Checks if the web request is done for the AP protocol
|
||||
|
|
@ -151,6 +153,7 @@ class ActivityPub
|
|||
$profile['outbox'] = $apcontact['outbox'];
|
||||
$profile['sharedinbox'] = $apcontact['sharedinbox'];
|
||||
$profile['photo'] = $apcontact['photo'];
|
||||
$profile['header'] = $apcontact['header'];
|
||||
$profile['account-type'] = self::getAccountType($apcontact);
|
||||
$profile['community'] = ($profile['account-type'] == User::ACCOUNT_TYPE_COMMUNITY);
|
||||
// $profile['keywords']
|
||||
|
|
@ -165,6 +168,10 @@ class ActivityPub
|
|||
$profile['baseurl'] = $apcontact['baseurl'];
|
||||
$profile['gsid'] = $apcontact['gsid'];
|
||||
|
||||
if (!is_null($apcontact['discoverable'])) {
|
||||
$profile['hide'] = !$apcontact['discoverable'];
|
||||
}
|
||||
|
||||
// Remove all "null" fields
|
||||
foreach ($profile as $field => $content) {
|
||||
if (is_null($content)) {
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ class Processor
|
|||
$data['url'] = $attachment['url'];
|
||||
$data['mimetype'] = $attachment['mediaType'];
|
||||
$data['height'] = $attachment['height'] ?? null;
|
||||
$data['width'] = $attachment['width'] ?? null;
|
||||
$data['size'] = $attachment['size'] ?? null;
|
||||
$data['preview'] = $attachment['image'] ?? null;
|
||||
$data['description'] = $attachment['name'] ?? null;
|
||||
|
|
@ -602,6 +603,12 @@ class Processor
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!($item['isForum'] ?? false) && ($receiver != 0) && ($item['gravity'] == GRAVITY_PARENT) &&
|
||||
($item['post-reason'] == Item::PR_BCC) && !Contact::isSharingByURL($activity['author'], $receiver)) {
|
||||
Logger::info('Top level post via BCC from a non sharer, ignoring', ['uid' => $receiver, 'contact' => $item['contact-id']]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (DI::pConfig()->get($receiver, 'system', 'accept_only_sharer', false) && ($receiver != 0) && ($item['gravity'] == GRAVITY_PARENT)) {
|
||||
$skip = !Contact::isSharingByURL($activity['author'], $receiver);
|
||||
|
||||
|
|
|
|||
|
|
@ -663,13 +663,14 @@ class Receiver
|
|||
}
|
||||
|
||||
if (!empty($actor)) {
|
||||
$profile = APContact::getByURL($actor);
|
||||
$profile = APContact::getByURL($actor);
|
||||
$followers = $profile['followers'] ?? '';
|
||||
|
||||
Logger::log('Actor: ' . $actor . ' - Followers: ' . $followers, Logger::DEBUG);
|
||||
$is_forum = ($actor['type'] ?? '') == 'Group';
|
||||
Logger::info('Got actor and followers', ['actor' => $actor, 'followers' => $followers]);
|
||||
} else {
|
||||
Logger::info('Empty actor', ['activity' => $activity]);
|
||||
$followers = '';
|
||||
$is_forum = false;
|
||||
}
|
||||
|
||||
// We have to prevent false follower assumptions upon thread completions
|
||||
|
|
@ -692,7 +693,7 @@ class Receiver
|
|||
}
|
||||
|
||||
// Fetch the receivers for the public and the followers collection
|
||||
if (in_array($receiver, [$followers, self::PUBLIC_COLLECTION]) && !empty($actor)) {
|
||||
if ((($receiver == $followers) || (($receiver == self::PUBLIC_COLLECTION) && !$is_forum)) && !empty($actor)) {
|
||||
$receivers = self::getReceiverForActor($actor, $tags, $receivers, $follower_target);
|
||||
continue;
|
||||
}
|
||||
|
|
@ -1111,14 +1112,6 @@ class Receiver
|
|||
'image' => $pageImage,
|
||||
];
|
||||
break;
|
||||
case 'as:Link':
|
||||
$attachlist[] = [
|
||||
'type' => str_replace('as:', '', JsonLD::fetchElement($attachment, '@type')),
|
||||
'mediaType' => JsonLD::fetchElement($attachment, 'as:mediaType', '@value'),
|
||||
'name' => JsonLD::fetchElement($attachment, 'as:name', '@value'),
|
||||
'url' => JsonLD::fetchElement($attachment, 'as:href', '@id')
|
||||
];
|
||||
break;
|
||||
case 'as:Image':
|
||||
$mediaType = JsonLD::fetchElement($attachment, 'as:mediaType', '@value');
|
||||
$imageFullUrl = JsonLD::fetchElement($attachment, 'as:url', '@id');
|
||||
|
|
@ -1180,7 +1173,10 @@ class Receiver
|
|||
'type' => str_replace('as:', '', JsonLD::fetchElement($attachment, '@type')),
|
||||
'mediaType' => JsonLD::fetchElement($attachment, 'as:mediaType', '@value'),
|
||||
'name' => JsonLD::fetchElement($attachment, 'as:name', '@value'),
|
||||
'url' => JsonLD::fetchElement($attachment, 'as:url', '@id')
|
||||
'url' => JsonLD::fetchElement($attachment, 'as:url', '@id'),
|
||||
'height' => JsonLD::fetchElement($attachment, 'as:height', '@value'),
|
||||
'width' => JsonLD::fetchElement($attachment, 'as:width', '@value'),
|
||||
'image' => JsonLD::fetchElement($attachment, 'as:image', '@id')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,13 +68,9 @@ class Transmitter
|
|||
*/
|
||||
public static function addRelayServerInboxes(array $inboxes = [])
|
||||
{
|
||||
$contacts = DBA::select('apcontact', ['inbox'],
|
||||
["`type` = ? AND `url` IN (SELECT `url` FROM `contact` WHERE `uid` = ? AND `rel` = ?)",
|
||||
'Application', 0, Contact::FRIEND]);
|
||||
while ($contact = DBA::fetch($contacts)) {
|
||||
foreach (Relay::getList(['inbox']) as $contact) {
|
||||
$inboxes[$contact['inbox']] = $contact['inbox'];
|
||||
}
|
||||
DBA::close($contacts);
|
||||
|
||||
return $inboxes;
|
||||
}
|
||||
|
|
@ -92,7 +88,7 @@ class Transmitter
|
|||
return $inboxes;
|
||||
}
|
||||
|
||||
$relays = Relay::getList($item_id, [], [Protocol::ACTIVITYPUB]);
|
||||
$relays = Relay::getDirectRelayList($item_id);
|
||||
if (empty($relays)) {
|
||||
return $inboxes;
|
||||
}
|
||||
|
|
@ -147,7 +143,7 @@ class Transmitter
|
|||
|
||||
return $success;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Collects a list of contacts of the given owner
|
||||
*
|
||||
|
|
@ -320,71 +316,66 @@ class Transmitter
|
|||
*/
|
||||
public static function getProfile($uid)
|
||||
{
|
||||
if ($uid != 0) {
|
||||
$condition = ['uid' => $uid, 'blocked' => false, 'account_expired' => false,
|
||||
'account_removed' => false, 'verified' => true];
|
||||
$fields = ['guid', 'nickname', 'pubkey', 'account-type', 'page-flags'];
|
||||
$user = DBA::selectFirst('user', $fields, $condition);
|
||||
if (!DBA::isResult($user)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$fields = ['locality', 'region', 'country-name'];
|
||||
$profile = DBA::selectFirst('profile', $fields, ['uid' => $uid]);
|
||||
if (!DBA::isResult($profile)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$fields = ['name', 'url', 'location', 'about', 'avatar', 'photo'];
|
||||
$contact = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]);
|
||||
if (!DBA::isResult($contact)) {
|
||||
return [];
|
||||
}
|
||||
} else {
|
||||
$contact = User::getSystemAccount();
|
||||
$user = ['guid' => '', 'nickname' => $contact['nick'], 'pubkey' => $contact['pubkey'],
|
||||
'account-type' => $contact['contact-type'], 'page-flags' => User::PAGE_FLAGS_NORMAL];
|
||||
$profile = ['locality' => '', 'region' => '', 'country-name' => ''];
|
||||
}
|
||||
$owner = User::getOwnerDataById($uid);
|
||||
|
||||
$data = ['@context' => ActivityPub::CONTEXT];
|
||||
$data['id'] = $contact['url'];
|
||||
$data['id'] = $owner['url'];
|
||||
|
||||
if (!empty($user['guid'])) {
|
||||
$data['diaspora:guid'] = $user['guid'];
|
||||
if (!empty($owner['guid'])) {
|
||||
$data['diaspora:guid'] = $owner['guid'];
|
||||
}
|
||||
|
||||
$data['type'] = ActivityPub::ACCOUNT_TYPES[$user['account-type']];
|
||||
|
||||
$data['type'] = ActivityPub::ACCOUNT_TYPES[$owner['account-type']];
|
||||
|
||||
if ($uid != 0) {
|
||||
$data['following'] = DI::baseUrl() . '/following/' . $user['nickname'];
|
||||
$data['followers'] = DI::baseUrl() . '/followers/' . $user['nickname'];
|
||||
$data['inbox'] = DI::baseUrl() . '/inbox/' . $user['nickname'];
|
||||
$data['outbox'] = DI::baseUrl() . '/outbox/' . $user['nickname'];
|
||||
$data['following'] = DI::baseUrl() . '/following/' . $owner['nick'];
|
||||
$data['followers'] = DI::baseUrl() . '/followers/' . $owner['nick'];
|
||||
$data['inbox'] = DI::baseUrl() . '/inbox/' . $owner['nick'];
|
||||
$data['outbox'] = DI::baseUrl() . '/outbox/' . $owner['nick'];
|
||||
} else {
|
||||
$data['inbox'] = DI::baseUrl() . '/friendica/inbox';
|
||||
}
|
||||
|
||||
$data['preferredUsername'] = $user['nickname'];
|
||||
$data['name'] = $contact['name'];
|
||||
$data['preferredUsername'] = $owner['nick'];
|
||||
$data['name'] = $owner['name'];
|
||||
|
||||
if (!empty($profile['country-name'] . $profile['region'] . $profile['locality'])) {
|
||||
$data['vcard:hasAddress'] = ['@type' => 'vcard:Home', 'vcard:country-name' => $profile['country-name'],
|
||||
'vcard:region' => $profile['region'], 'vcard:locality' => $profile['locality']];
|
||||
if (!empty($owner['country-name'] . $owner['region'] . $owner['locality'])) {
|
||||
$data['vcard:hasAddress'] = ['@type' => 'vcard:Home', 'vcard:country-name' => $owner['country-name'],
|
||||
'vcard:region' => $owner['region'], 'vcard:locality' => $owner['locality']];
|
||||
}
|
||||
|
||||
if (!empty($contact['about'])) {
|
||||
$data['summary'] = BBCode::convert($contact['about'], false);
|
||||
if (!empty($owner['about'])) {
|
||||
$data['summary'] = BBCode::convert($owner['about'], false);
|
||||
}
|
||||
|
||||
$data['url'] = $contact['url'];
|
||||
$data['manuallyApprovesFollowers'] = in_array($user['page-flags'], [User::PAGE_FLAGS_NORMAL, User::PAGE_FLAGS_PRVGROUP]);
|
||||
$data['publicKey'] = ['id' => $contact['url'] . '#main-key',
|
||||
'owner' => $contact['url'],
|
||||
'publicKeyPem' => $user['pubkey']];
|
||||
$data['url'] = $owner['url'];
|
||||
$data['manuallyApprovesFollowers'] = in_array($owner['page-flags'], [User::PAGE_FLAGS_NORMAL, User::PAGE_FLAGS_PRVGROUP]);
|
||||
$data['discoverable'] = $owner['net-publish'];
|
||||
$data['publicKey'] = ['id' => $owner['url'] . '#main-key',
|
||||
'owner' => $owner['url'],
|
||||
'publicKeyPem' => $owner['pubkey']];
|
||||
$data['endpoints'] = ['sharedInbox' => DI::baseUrl() . '/inbox'];
|
||||
$data['icon'] = ['type' => 'Image',
|
||||
'url' => $contact['photo']];
|
||||
$data['icon'] = ['type' => 'Image', 'url' => Contact::getAvatarUrlForId($owner['id'], '', $owner['updated'])];
|
||||
|
||||
$resourceid = Photo::ridFromURI($owner['photo']);
|
||||
if (!empty($resourceid)) {
|
||||
$photo = Photo::selectFirst(['type'], ["resource-id" => $resourceid]);
|
||||
if (!empty($photo['type'])) {
|
||||
$data['icon']['mediaType'] = $photo['type'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($owner['header'])) {
|
||||
$data['image'] = ['type' => 'Image', 'url' => Contact::getHeaderUrlForId($owner['id'], '', $owner['updated'])];
|
||||
|
||||
$resourceid = Photo::ridFromURI($owner['header']);
|
||||
if (!empty($resourceid)) {
|
||||
$photo = Photo::selectFirst(['type'], ["resource-id" => $resourceid]);
|
||||
if (!empty($photo['type'])) {
|
||||
$data['image']['mediaType'] = $photo['type'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data['generator'] = self::getService();
|
||||
|
||||
|
|
@ -533,6 +524,8 @@ class Transmitter
|
|||
$actor_profile = APContact::getByURL($item['author-link']);
|
||||
}
|
||||
|
||||
$exclusive = false;
|
||||
|
||||
$terms = Tag::getByURIId($item['uri-id'], [Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION]);
|
||||
|
||||
if ($item['private'] != Item::PRIVATE) {
|
||||
|
|
@ -557,6 +550,12 @@ class Transmitter
|
|||
foreach ($terms as $term) {
|
||||
$profile = APContact::getByURL($term['url'], false);
|
||||
if (!empty($profile)) {
|
||||
if ($term['type'] == Tag::EXCLUSIVE_MENTION) {
|
||||
$exclusive = true;
|
||||
if (!empty($profile['followers']) && ($profile['type'] == 'Group')) {
|
||||
$data['cc'][] = $profile['followers'];
|
||||
}
|
||||
}
|
||||
$data['to'][] = $profile['url'];
|
||||
}
|
||||
}
|
||||
|
|
@ -604,7 +603,7 @@ class Transmitter
|
|||
// But comments to forums aren't directed to the followers collection
|
||||
// This rule is only valid when the actor isn't the forum.
|
||||
// The forum needs to transmit their content to their followers.
|
||||
if (($profile['type'] == 'Group') && ($profile['url'] != $actor_profile['url'])) {
|
||||
if (($profile['type'] == 'Group') && ($profile['url'] != ($actor_profile['url'] ?? ''))) {
|
||||
$data['to'][] = $profile['url'];
|
||||
} else {
|
||||
$data['cc'][] = $profile['url'];
|
||||
|
|
@ -612,8 +611,10 @@ class Transmitter
|
|||
$data['cc'][] = $actor_profile['followers'];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Public thread parent post always are directed to the followers
|
||||
} elseif (!$exclusive) {
|
||||
// Public thread parent post always are directed to the followers.
|
||||
// This mustn't be done by posts that are directed to forum servers via the exclusive mention.
|
||||
// But possibly in that case we could add the "followers" collection of the forum to the message.
|
||||
if (($item['private'] != Item::PRIVATE) && !$forum_mode) {
|
||||
$data['cc'][] = $actor_profile['followers'];
|
||||
}
|
||||
|
|
@ -694,10 +695,10 @@ class Transmitter
|
|||
/**
|
||||
* Check if a given contact should be delivered via AP
|
||||
*
|
||||
* @param array $contact
|
||||
* @param array $networks
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
* @param array $contact
|
||||
* @param array $networks
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function isAPContact(array $contact, array $networks)
|
||||
{
|
||||
|
|
@ -748,10 +749,6 @@ class Transmitter
|
|||
|
||||
$contacts = DBA::select('contact', ['id', 'url', 'network', 'protocol', 'gsid'], $condition);
|
||||
while ($contact = DBA::fetch($contacts)) {
|
||||
if (Contact::isLocal($contact['url'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!self::isAPContact($contact, $networks)) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -766,7 +763,7 @@ class Transmitter
|
|||
|
||||
$profile = APContact::getByURL($contact['url'], false);
|
||||
if (!empty($profile)) {
|
||||
if (empty($profile['sharedinbox']) || $personal) {
|
||||
if (empty($profile['sharedinbox']) || $personal || Contact::isLocal($contact['url'])) {
|
||||
$target = $profile['inbox'];
|
||||
} else {
|
||||
$target = $profile['sharedinbox'];
|
||||
|
|
@ -829,15 +826,11 @@ class Transmitter
|
|||
if ($item_profile && ($receiver == $item_profile['followers']) && ($uid == $profile_uid)) {
|
||||
$inboxes = array_merge($inboxes, self::fetchTargetInboxesforUser($uid, $personal, self::isAPPost($last_id)));
|
||||
} else {
|
||||
if (Contact::isLocal($receiver)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$profile = APContact::getByURL($receiver, false);
|
||||
if (!empty($profile)) {
|
||||
$contact = Contact::getByURLForUser($receiver, $uid, false, ['id']);
|
||||
|
||||
if (empty($profile['sharedinbox']) || $personal || $blindcopy) {
|
||||
if (empty($profile['sharedinbox']) || $personal || $blindcopy || Contact::isLocal($receiver)) {
|
||||
$target = $profile['inbox'];
|
||||
} else {
|
||||
$target = $profile['sharedinbox'];
|
||||
|
|
@ -868,24 +861,19 @@ class Transmitter
|
|||
return [];
|
||||
}
|
||||
|
||||
$mail['uri-id'] = ItemURI::insert(['uri' => $mail['uri'], 'guid' => $mail['guid']]);
|
||||
|
||||
$reply = DBA::selectFirst('mail', ['uri', 'from-url', 'guid'], ['parent-uri' => $mail['parent-uri'], 'reply' => false]);
|
||||
$reply = DBA::selectFirst('mail', ['uri', 'uri-id', 'from-url'], ['parent-uri' => $mail['parent-uri'], 'reply' => false]);
|
||||
|
||||
// Making the post more compatible for Mastodon by:
|
||||
// - Making it a note and not an article (no title)
|
||||
// - Moving the title into the "summary" field that is used as a "content warning"
|
||||
|
||||
if ($use_title) {
|
||||
$mail['body'] = $mail['body'];
|
||||
$mail['title'] = $mail['title'];
|
||||
} else {
|
||||
if (!$use_title) {
|
||||
$mail['body'] = '[abstract]' . $mail['title'] . "[/abstract]\n" . $mail['body'];
|
||||
$mail['title'] = '';
|
||||
}
|
||||
|
||||
$mail['author-link'] = $mail['owner-link'] = $mail['from-url'];
|
||||
$mail['author-id'] = Contact::getIdForURL($mail['author-link'], 0, false);
|
||||
$mail['owner-id'] = $mail['author-id'];
|
||||
$mail['allow_cid'] = '<'.$mail['contact-id'].'>';
|
||||
$mail['allow_gid'] = '';
|
||||
$mail['deny_cid'] = '';
|
||||
|
|
@ -893,9 +881,9 @@ class Transmitter
|
|||
$mail['private'] = Item::PRIVATE;
|
||||
$mail['deleted'] = false;
|
||||
$mail['edited'] = $mail['created'];
|
||||
$mail['plink'] = $mail['uri'];
|
||||
$mail['thr-parent'] = $reply['uri'];
|
||||
$mail['thr-parent-id'] = ItemURI::insert(['uri' => $reply['uri'], 'guid' => $reply['guid']]);
|
||||
$mail['plink'] = DI::baseUrl() . '/message/' . $mail['id'];
|
||||
$mail['parent-uri'] = $reply['uri'];
|
||||
$mail['parent-uri-id'] = $reply['uri-id'];
|
||||
$mail['parent-author-id'] = Contact::getIdForURL($reply['from-url'], 0, false);
|
||||
$mail['gravity'] = ($mail['reply'] ? GRAVITY_COMMENT: GRAVITY_PARENT);
|
||||
$mail['event-type'] = '';
|
||||
|
|
@ -1066,7 +1054,7 @@ class Transmitter
|
|||
if (!empty($self['uid'])) {
|
||||
$forum_item = Post::selectFirst(Item::DELIVER_FIELDLIST, ['uri-id' => $item['uri-id'], 'uid' => $self['uid']]);
|
||||
if (DBA::isResult($forum_item)) {
|
||||
$item = $forum_item;
|
||||
$item = $forum_item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1285,10 +1273,24 @@ class Transmitter
|
|||
}
|
||||
$urls[] = $attachment['url'];
|
||||
|
||||
$attachments[] = ['type' => 'Document',
|
||||
$attach = ['type' => 'Document',
|
||||
'mediaType' => $attachment['mimetype'],
|
||||
'url' => $attachment['url'],
|
||||
'name' => $attachment['description']];
|
||||
|
||||
if (!empty($attachment['height'])) {
|
||||
$attach['height'] = $attachment['height'];
|
||||
}
|
||||
|
||||
if (!empty($attachment['width'])) {
|
||||
$attach['width'] = $attachment['width'];
|
||||
}
|
||||
|
||||
if (!empty($attachment['preview'])) {
|
||||
$attach['image'] = $attachment['preview'];
|
||||
}
|
||||
|
||||
$attachments[] = $attach;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1303,10 +1305,24 @@ class Transmitter
|
|||
}
|
||||
$urls[] = $attachment['url'];
|
||||
|
||||
$attachments[] = ['type' => 'Document',
|
||||
$attach = ['type' => 'Document',
|
||||
'mediaType' => $attachment['mimetype'],
|
||||
'url' => $attachment['url'],
|
||||
'name' => $attachment['description']];
|
||||
|
||||
if (!empty($attachment['height'])) {
|
||||
$attach['height'] = $attachment['height'];
|
||||
}
|
||||
|
||||
if (!empty($attachment['width'])) {
|
||||
$attach['width'] = $attachment['width'];
|
||||
}
|
||||
|
||||
if (!empty($attachment['preview'])) {
|
||||
$attach['image'] = $attachment['preview'];
|
||||
}
|
||||
|
||||
$attachments[] = $attach;
|
||||
}
|
||||
// Currently deactivated, since it creates side effects on Mastodon and Pleroma.
|
||||
// It will be activated, once this cleared.
|
||||
|
|
@ -1530,12 +1546,21 @@ class Transmitter
|
|||
|
||||
if ($type == 'Note') {
|
||||
$body = $item['raw-body'] ?? self::removePictures($body);
|
||||
} elseif (($type == 'Article') && empty($data['summary'])) {
|
||||
$regexp = "/[@!]\[url\=([^\[\]]*)\].*?\[\/url\]/ism";
|
||||
$summary = preg_replace_callback($regexp, ['self', 'mentionAddrCallback'], $body);
|
||||
$data['summary'] = BBCode::toPlaintext(Plaintext::shorten(self::removePictures($summary), 1000));
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Improve the automated summary
|
||||
* This part is currently deactivated. The automated summary seems to be more
|
||||
* confusing than helping. But possibly we will find a better way.
|
||||
* So the code is left here for now as a reminder
|
||||
*
|
||||
* } elseif (($type == 'Article') && empty($data['summary'])) {
|
||||
* $regexp = "/[@!]\[url\=([^\[\]]*)\].*?\[\/url\]/ism";
|
||||
* $summary = preg_replace_callback($regexp, ['self', 'mentionAddrCallback'], $body);
|
||||
* $data['summary'] = BBCode::toPlaintext(Plaintext::shorten(self::removePictures($summary), 1000));
|
||||
* }
|
||||
*/
|
||||
|
||||
if (empty($item['uid']) || !Feature::isEnabled($item['uid'], 'explicit_mentions')) {
|
||||
$body = self::prependMentions($body, $item['uri-id'], $item['author-link']);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ use Friendica\Util\Crypto;
|
|||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Images;
|
||||
use Friendica\Util\Network;
|
||||
use Friendica\Util\Proxy;
|
||||
use Friendica\Util\Strings;
|
||||
use Friendica\Util\XML;
|
||||
|
||||
|
|
@ -618,7 +619,8 @@ class DFRN
|
|||
XML::addElement($doc, $author, "dfrn:handle", $owner["addr"], $attributes);
|
||||
|
||||
$attributes = ["rel" => "photo", "type" => "image/jpeg",
|
||||
"media:width" => 300, "media:height" => 300, "href" => $owner['photo']];
|
||||
"media:width" => Proxy::PIXEL_SMALL, "media:height" => Proxy::PIXEL_SMALL,
|
||||
"href" => Contact::getAvatarUrlForId($owner['id'], Proxy::SIZE_SMALL, $owner['updated'])];
|
||||
|
||||
if (!$public || !$hide) {
|
||||
$attributes["dfrn:updated"] = $picdate;
|
||||
|
|
|
|||
|
|
@ -4049,13 +4049,11 @@ class Diaspora
|
|||
return false;
|
||||
}
|
||||
|
||||
$parent = Post::selectFirst(['parent-uri'], ['uri' => $item['thr-parent']]);
|
||||
if (!DBA::isResult($parent)) {
|
||||
return;
|
||||
// This is only needed for the automated tests
|
||||
if (empty($owner['uprvkey'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$item['parent-uri'] = $parent['parent-uri'];
|
||||
|
||||
$message = self::constructComment($item, $owner);
|
||||
if ($message === false) {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ use Friendica\Model\User;
|
|||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Network;
|
||||
use Friendica\Util\ParseUrl;
|
||||
use Friendica\Util\Proxy;
|
||||
use Friendica\Util\Strings;
|
||||
use Friendica\Util\XML;
|
||||
|
||||
|
|
@ -957,7 +958,7 @@ class Feed
|
|||
XML::addElement($doc, $root, "id", DI::baseUrl() . "/profile/" . $owner["nick"]);
|
||||
XML::addElement($doc, $root, "title", $title);
|
||||
XML::addElement($doc, $root, "subtitle", sprintf("Updates from %s on %s", $owner["name"], DI::config()->get('config', 'sitename')));
|
||||
XML::addElement($doc, $root, "logo", $owner["photo"]);
|
||||
XML::addElement($doc, $root, "logo", Contact::getAvatarUrlForId($owner['id'], Proxy::SIZE_SMALL, $owner['updated']));
|
||||
XML::addElement($doc, $root, "updated", DateTimeFormat::utcNow(DateTimeFormat::ATOM));
|
||||
|
||||
$author = self::addAuthor($doc, $owner);
|
||||
|
|
|
|||
|
|
@ -1272,7 +1272,7 @@ class OStatus
|
|||
XML::addElement($doc, $root, "id", DI::baseUrl() . "/profile/" . $owner["nick"]);
|
||||
XML::addElement($doc, $root, "title", $title);
|
||||
XML::addElement($doc, $root, "subtitle", sprintf("Updates from %s on %s", $owner["name"], DI::config()->get('config', 'sitename')));
|
||||
XML::addElement($doc, $root, "logo", $owner["photo"]);
|
||||
XML::addElement($doc, $root, "logo", Contact::getAvatarUrlForId($owner['id'], ProxyUtils::SIZE_SMALL, $owner['updated']));
|
||||
XML::addElement($doc, $root, "updated", DateTimeFormat::utcNow(DateTimeFormat::ATOM));
|
||||
|
||||
$author = self::addAuthor($doc, $owner, true);
|
||||
|
|
@ -1427,18 +1427,18 @@ class OStatus
|
|||
$attributes = [
|
||||
"rel" => "avatar",
|
||||
"type" => "image/jpeg", // To-Do?
|
||||
"media:width" => 300,
|
||||
"media:height" => 300,
|
||||
"href" => $owner["photo"]];
|
||||
"media:width" => ProxyUtils::PIXEL_SMALL,
|
||||
"media:height" => ProxyUtils::PIXEL_SMALL,
|
||||
"href" => Contact::getAvatarUrlForId($owner['id'], ProxyUtils::SIZE_SMALL, $owner['updated'])];
|
||||
XML::addElement($doc, $author, "link", "", $attributes);
|
||||
|
||||
if (isset($owner["thumb"])) {
|
||||
$attributes = [
|
||||
"rel" => "avatar",
|
||||
"type" => "image/jpeg", // To-Do?
|
||||
"media:width" => 80,
|
||||
"media:height" => 80,
|
||||
"href" => $owner["thumb"]];
|
||||
"media:width" => ProxyUtils::PIXEL_THUMB,
|
||||
"media:height" => ProxyUtils::PIXEL_THUMB,
|
||||
"href" => Contact::getAvatarUrlForId($owner['id'], ProxyUtils::SIZE_THUMB, $owner['updated'])];
|
||||
XML::addElement($doc, $author, "link", "", $attributes);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ use Friendica\DI;
|
|||
use Friendica\Model\APContact;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\GServer;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Model\Search;
|
||||
use Friendica\Model\Tag;
|
||||
|
|
@ -54,12 +53,7 @@ class Relay
|
|||
{
|
||||
$config = DI::config();
|
||||
|
||||
$subscribe = $config->get('system', 'relay_subscribe', false);
|
||||
if ($subscribe) {
|
||||
$scope = $config->get('system', 'relay_scope', SR_SCOPE_ALL);
|
||||
} else {
|
||||
$scope = SR_SCOPE_NONE;
|
||||
}
|
||||
$scope = $config->get('system', 'relay_scope');
|
||||
|
||||
if ($scope == SR_SCOPE_NONE) {
|
||||
Logger::info('Server does not accept relay posts - rejected', ['network' => $network, 'url' => $url]);
|
||||
|
|
@ -168,7 +162,7 @@ class Relay
|
|||
return;
|
||||
}
|
||||
|
||||
if (DBA::isResult($old)) {
|
||||
if (DBA::isResult($old)) {
|
||||
$fields['updated'] = DateTimeFormat::utcNow();
|
||||
|
||||
Logger::info('Update relay contact', ['server' => $gserver['url'], 'id' => $old['id'], 'fields' => $fields]);
|
||||
|
|
@ -224,76 +218,65 @@ class Relay
|
|||
}
|
||||
|
||||
/**
|
||||
* Return a list of relay servers
|
||||
*
|
||||
* The list contains not only the official relays but also servers that we serve directly
|
||||
* Return a list of servers that we serve via the direct relay
|
||||
*
|
||||
* @param integer $item_id id of the item that is sent
|
||||
* @param array $contacts Previously fetched contacts
|
||||
* @param array $networks Networks of the relay servers
|
||||
* @param array $networks Networks of the relay servers
|
||||
*
|
||||
* @return array of relay servers
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function getList(int $item_id, array $contacts, array $networks)
|
||||
public static function getDirectRelayList(int $item_id)
|
||||
{
|
||||
$serverlist = [];
|
||||
|
||||
// Fetching relay servers
|
||||
$serverdata = DI::config()->get("system", "relay_server");
|
||||
|
||||
if (!empty($serverdata)) {
|
||||
$servers = explode(",", $serverdata);
|
||||
foreach ($servers as $server) {
|
||||
$gserver = DBA::selectFirst('gserver', ['id', 'url', 'network'], ['nurl' => Strings::normaliseLink($server)]);
|
||||
if (DBA::isResult($gserver)) {
|
||||
$serverlist[$gserver['id']] = $gserver;
|
||||
}
|
||||
}
|
||||
if (!DI::config()->get("system", "relay_directly", false)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (DI::config()->get("system", "relay_directly", false)) {
|
||||
// We distribute our stuff based on the parent to ensure that the thread will be complete
|
||||
$parent = Post::selectFirst(['uri-id'], ['id' => $item_id]);
|
||||
if (!DBA::isResult($parent)) {
|
||||
return;
|
||||
}
|
||||
// We distribute our stuff based on the parent to ensure that the thread will be complete
|
||||
$parent = Post::selectFirst(['uri-id'], ['id' => $item_id]);
|
||||
if (!DBA::isResult($parent)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Servers that want to get all content
|
||||
$servers = DBA::select('gserver', ['id', 'url', 'network'], ['relay-subscribe' => true, 'relay-scope' => 'all']);
|
||||
// Servers that want to get all content
|
||||
$servers = DBA::select('gserver', ['id', 'url', 'network'], ['relay-subscribe' => true, 'relay-scope' => 'all']);
|
||||
while ($server = DBA::fetch($servers)) {
|
||||
$serverlist[$server['id']] = $server;
|
||||
}
|
||||
DBA::close($servers);
|
||||
|
||||
// All tags of the current post
|
||||
$tags = DBA::select('tag-view', ['name'], ['uri-id' => $parent['uri-id'], 'type' => Tag::HASHTAG]);
|
||||
$taglist = [];
|
||||
while ($tag = DBA::fetch($tags)) {
|
||||
$taglist[] = $tag['name'];
|
||||
}
|
||||
DBA::close($tags);
|
||||
|
||||
// All servers who wants content with this tag
|
||||
$tagserverlist = [];
|
||||
if (!empty($taglist)) {
|
||||
$tagserver = DBA::select('gserver-tag', ['gserver-id'], ['tag' => $taglist]);
|
||||
while ($server = DBA::fetch($tagserver)) {
|
||||
$tagserverlist[] = $server['gserver-id'];
|
||||
}
|
||||
DBA::close($tagserver);
|
||||
}
|
||||
|
||||
// All adresses with the given id
|
||||
if (!empty($tagserverlist)) {
|
||||
$servers = DBA::select('gserver', ['id', 'url', 'network'], ['relay-subscribe' => true, 'relay-scope' => 'tags', 'id' => $tagserverlist]);
|
||||
while ($server = DBA::fetch($servers)) {
|
||||
$serverlist[$server['id']] = $server;
|
||||
}
|
||||
DBA::close($servers);
|
||||
|
||||
// All tags of the current post
|
||||
$tags = DBA::select('tag-view', ['name'], ['uri-id' => $parent['uri-id'], 'type' => Tag::HASHTAG]);
|
||||
$taglist = [];
|
||||
while ($tag = DBA::fetch($tags)) {
|
||||
$taglist[] = $tag['name'];
|
||||
}
|
||||
DBA::close($tags);
|
||||
|
||||
// All servers who wants content with this tag
|
||||
$tagserverlist = [];
|
||||
if (!empty($taglist)) {
|
||||
$tagserver = DBA::select('gserver-tag', ['gserver-id'], ['tag' => $taglist]);
|
||||
while ($server = DBA::fetch($tagserver)) {
|
||||
$tagserverlist[] = $server['gserver-id'];
|
||||
}
|
||||
DBA::close($tagserver);
|
||||
}
|
||||
|
||||
// All adresses with the given id
|
||||
if (!empty($tagserverlist)) {
|
||||
$servers = DBA::select('gserver', ['id', 'url', 'network'], ['relay-subscribe' => true, 'relay-scope' => 'tags', 'id' => $tagserverlist]);
|
||||
while ($server = DBA::fetch($servers)) {
|
||||
$serverlist[$server['id']] = $server;
|
||||
}
|
||||
DBA::close($servers);
|
||||
}
|
||||
}
|
||||
|
||||
$contacts = [];
|
||||
|
||||
// Now we are collecting all relay contacts
|
||||
foreach ($serverlist as $gserver) {
|
||||
// We don't send messages to ourselves
|
||||
|
|
@ -304,15 +287,24 @@ class Relay
|
|||
if (empty($contact)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($contact['network'], $networks) && !in_array($contact['batch'], array_column($contacts, 'batch'))) {
|
||||
$contacts[] = $contact;
|
||||
}
|
||||
}
|
||||
|
||||
return $contacts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of relay servers
|
||||
*
|
||||
* @param array $fields Field list
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getList($fields = []):array
|
||||
{
|
||||
return DBA::selectToArray('apcontact', $fields,
|
||||
["`type` = ? AND `url` IN (SELECT `url` FROM `contact` WHERE `uid` = ? AND `rel` = ?)", 'Application', 0, Contact::FRIEND]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a contact for a given server address or creates a dummy entry
|
||||
*
|
||||
|
|
|
|||
|
|
@ -87,8 +87,13 @@ class Notification extends BaseRepository
|
|||
public function setSeen(bool $seen = true, Model\Notification $notify = null)
|
||||
{
|
||||
if (empty($notify)) {
|
||||
$this->dba->update('notification', ['seen' => $seen], ['uid' => local_user()]);
|
||||
$conditions = ['uid' => local_user()];
|
||||
} else {
|
||||
if (!empty($notify->{'uri-id'})) {
|
||||
$this->dba->update('notification', ['seen' => $seen], ['uid' => local_user(), 'target-uri-id' => $notify->{'uri-id'}]);
|
||||
}
|
||||
|
||||
$conditions = ['(`link` = ? OR (`parent` != 0 AND `parent` = ? AND `otype` = ?)) AND `uid` = ?',
|
||||
$notify->link,
|
||||
$notify->parent,
|
||||
|
|
|
|||
|
|
@ -342,8 +342,10 @@ class Authentication
|
|||
$this->dba->update('user', ['login_date' => DateTimeFormat::utcNow()], ['uid' => $user_record['uid']]);
|
||||
|
||||
// Set the login date for all identities of the user
|
||||
$this->dba->update('user', ['login_date' => DateTimeFormat::utcNow()],
|
||||
['parent-uid' => $masterUid, 'account_removed' => false]);
|
||||
if (!empty($masterUid)) {
|
||||
$this->dba->update('user', ['login_date' => DateTimeFormat::utcNow()],
|
||||
['parent-uid' => $masterUid, 'account_removed' => false]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($login_initial) {
|
||||
|
|
|
|||
196
src/Security/BasicAuth.php
Normal file
196
src/Security/BasicAuth.php
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2021, the Friendica project
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Security;
|
||||
|
||||
use Exception;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\Session;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Network\HTTPException\UnauthorizedException;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
||||
/**
|
||||
* Authentification via the basic auth method
|
||||
*/
|
||||
class BasicAuth
|
||||
{
|
||||
/**
|
||||
* @var bool|int
|
||||
*/
|
||||
protected static $current_user_id = 0;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $current_token = [];
|
||||
|
||||
/**
|
||||
* Get current user id, returns 0 if $login is set to false and not logged in.
|
||||
* When $login is true, the execution will stop when not logged in.
|
||||
*
|
||||
* @param bool $login Perform a login request if "true"
|
||||
*
|
||||
* @return int User ID
|
||||
*/
|
||||
public static function getCurrentUserID(bool $login)
|
||||
{
|
||||
if (empty(self::$current_user_id)) {
|
||||
self::$current_user_id = self::getUserIdByAuth($login);
|
||||
}
|
||||
|
||||
return (int)self::$current_user_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a dummy application token
|
||||
*
|
||||
* @return array token
|
||||
*/
|
||||
public static function getCurrentApplicationToken()
|
||||
{
|
||||
if (empty(self::getCurrentUserID(true))) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!empty(self::$current_token)) {
|
||||
return self::$current_token;
|
||||
}
|
||||
|
||||
$source = $_REQUEST['source'] ?? '';
|
||||
|
||||
// Support for known clients that doesn't send a source name
|
||||
if (empty($source) && !empty($_SERVER['HTTP_USER_AGENT'])) {
|
||||
if(strpos($_SERVER['HTTP_USER_AGENT'], "Twidere") !== false) {
|
||||
$source = 'Twidere';
|
||||
}
|
||||
|
||||
Logger::info('Unrecognized user-agent', ['http_user_agent' => $_SERVER['HTTP_USER_AGENT']]);
|
||||
} else {
|
||||
Logger::info('Empty user-agent');
|
||||
}
|
||||
|
||||
if (empty($source)) {
|
||||
$source = 'api';
|
||||
}
|
||||
|
||||
self::$current_token = [
|
||||
'uid' => self::$current_user_id,
|
||||
'id' => 0,
|
||||
'name' => $source,
|
||||
'website' => '',
|
||||
'created_at' => DBA::NULL_DATETIME,
|
||||
'read' => true,
|
||||
'write' => true,
|
||||
'follow' => true,
|
||||
'push' => false];
|
||||
|
||||
return self::$current_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the user id via the auth header information
|
||||
*
|
||||
* @param boolean $do_login Perform a login request if not logged in
|
||||
*
|
||||
* @return integer User ID
|
||||
*/
|
||||
private static function getUserIdByAuth(bool $do_login = true):int
|
||||
{
|
||||
$a = DI::app();
|
||||
Session::set('allow_api', false);
|
||||
self::$current_user_id = 0;
|
||||
|
||||
// workaround for HTTP-auth in CGI mode
|
||||
if (!empty($_SERVER['REDIRECT_REMOTE_USER'])) {
|
||||
$userpass = base64_decode(substr($_SERVER["REDIRECT_REMOTE_USER"], 6));
|
||||
if (!empty($userpass) && strpos($userpass, ':')) {
|
||||
list($name, $password) = explode(':', $userpass);
|
||||
$_SERVER['PHP_AUTH_USER'] = $name;
|
||||
$_SERVER['PHP_AUTH_PW'] = $password;
|
||||
}
|
||||
}
|
||||
|
||||
$user = $_SERVER['PHP_AUTH_USER'] ?? '';
|
||||
$password = $_SERVER['PHP_AUTH_PW'] ?? '';
|
||||
|
||||
// allow "user@server" login (but ignore 'server' part)
|
||||
$at = strstr($user, "@", true);
|
||||
if ($at) {
|
||||
$user = $at;
|
||||
}
|
||||
|
||||
// next code from mod/auth.php. needs better solution
|
||||
$record = null;
|
||||
|
||||
$addon_auth = [
|
||||
'username' => trim($user),
|
||||
'password' => trim($password),
|
||||
'authenticated' => 0,
|
||||
'user_record' => null,
|
||||
];
|
||||
|
||||
/*
|
||||
* An addon indicates successful login by setting 'authenticated' to non-zero value and returning a user record
|
||||
* Addons should never set 'authenticated' except to indicate success - as hooks may be chained
|
||||
* and later addons should not interfere with an earlier one that succeeded.
|
||||
*/
|
||||
Hook::callAll('authenticate', $addon_auth);
|
||||
|
||||
if ($addon_auth['authenticated'] && !empty($addon_auth['user_record'])) {
|
||||
$record = $addon_auth['user_record'];
|
||||
} else {
|
||||
try {
|
||||
$user_id = User::getIdFromPasswordAuthentication(trim($user), trim($password), true);
|
||||
$record = DBA::selectFirst('user', [], ['uid' => $user_id]);
|
||||
} catch (Exception $ex) {
|
||||
$record = [];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($record)) {
|
||||
if (!$do_login) {
|
||||
return 0;
|
||||
}
|
||||
Logger::debug('Access denied', ['parameters' => $_SERVER]);
|
||||
header('WWW-Authenticate: Basic realm="Friendica"');
|
||||
throw new UnauthorizedException("This API requires login");
|
||||
}
|
||||
|
||||
// Don't refresh the login date more often than twice a day to spare database writes
|
||||
$login_refresh = strcmp(DateTimeFormat::utc('now - 12 hours'), $record['login_date']) > 0;
|
||||
|
||||
DI::auth()->setForUser($a, $record, false, false, $login_refresh);
|
||||
|
||||
Session::set('allow_api', true);
|
||||
|
||||
Hook::callAll('logged_in', $a->user);
|
||||
|
||||
if (Session::get('allow_api')) {
|
||||
self::$current_user_id = local_user();
|
||||
} else {
|
||||
self::$current_user_id = 0;
|
||||
}
|
||||
return self::$current_user_id;
|
||||
}
|
||||
}
|
||||
193
src/Security/OAuth.php
Normal file
193
src/Security/OAuth.php
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2021, the Friendica project
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Security;
|
||||
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Module\BaseApi;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
||||
/**
|
||||
* OAuth Server
|
||||
*/
|
||||
class OAuth
|
||||
{
|
||||
/**
|
||||
* @var bool|int
|
||||
*/
|
||||
protected static $current_user_id = 0;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $current_token = [];
|
||||
|
||||
/**
|
||||
* Get current user id, returns 0 if not logged in
|
||||
*
|
||||
* @return int User ID
|
||||
*/
|
||||
public static function getCurrentUserID()
|
||||
{
|
||||
if (empty(self::$current_user_id)) {
|
||||
$token = self::getCurrentApplicationToken();
|
||||
if (!empty($token['uid'])) {
|
||||
self::$current_user_id = $token['uid'];
|
||||
} else {
|
||||
self::$current_user_id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return (int)self::$current_user_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current application token
|
||||
*
|
||||
* @return array token
|
||||
*/
|
||||
public static function getCurrentApplicationToken()
|
||||
{
|
||||
if (empty(self::$current_token)) {
|
||||
self::$current_token = self::getTokenByBearer();
|
||||
}
|
||||
|
||||
return self::$current_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user token via the Bearer token
|
||||
*
|
||||
* @return array User Token
|
||||
*/
|
||||
private static function getTokenByBearer()
|
||||
{
|
||||
$authorization = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
|
||||
|
||||
if (empty($authorization)) {
|
||||
// workaround for HTTP-auth in CGI mode
|
||||
$authorization = $_SERVER['REDIRECT_REMOTE_USER'] ?? '';
|
||||
}
|
||||
|
||||
if (substr($authorization, 0, 7) != 'Bearer ') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$condition = ['access_token' => trim(substr($authorization, 7))];
|
||||
|
||||
$token = DBA::selectFirst('application-view', ['uid', 'id', 'name', 'website', 'created_at', 'read', 'write', 'follow', 'push'], $condition);
|
||||
if (!DBA::isResult($token)) {
|
||||
Logger::warning('Token not found', $condition);
|
||||
return [];
|
||||
}
|
||||
Logger::debug('Token found', $token);
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the application record via the provided request header fields
|
||||
*
|
||||
* @param string $client_id
|
||||
* @param string $client_secret
|
||||
* @param string $redirect_uri
|
||||
* @return array application record
|
||||
*/
|
||||
public static function getApplication(string $client_id, string $client_secret, string $redirect_uri)
|
||||
{
|
||||
$condition = ['client_id' => $client_id];
|
||||
if (!empty($client_secret)) {
|
||||
$condition['client_secret'] = $client_secret;
|
||||
}
|
||||
if (!empty($redirect_uri)) {
|
||||
$condition['redirect_uri'] = $redirect_uri;
|
||||
}
|
||||
|
||||
$application = DBA::selectFirst('application', [], $condition);
|
||||
if (!DBA::isResult($application)) {
|
||||
Logger::warning('Application not found', $condition);
|
||||
return [];
|
||||
}
|
||||
return $application;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an token for the application and user exists
|
||||
*
|
||||
* @param array $application
|
||||
* @param integer $uid
|
||||
* @return boolean
|
||||
*/
|
||||
public static function existsTokenForUser(array $application, int $uid)
|
||||
{
|
||||
return DBA::exists('application-token', ['application-id' => $application['id'], 'uid' => $uid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the token for the given application and user
|
||||
*
|
||||
* @param array $application
|
||||
* @param integer $uid
|
||||
* @return array application record
|
||||
*/
|
||||
public static function getTokenForUser(array $application, int $uid)
|
||||
{
|
||||
return DBA::selectFirst('application-token', [], ['application-id' => $application['id'], 'uid' => $uid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and fetch an token for the application and user
|
||||
*
|
||||
* @param array $application
|
||||
* @param integer $uid
|
||||
* @param string $scope
|
||||
* @return array application record
|
||||
*/
|
||||
public static function createTokenForUser(array $application, int $uid, string $scope)
|
||||
{
|
||||
$code = bin2hex(random_bytes(32));
|
||||
$access_token = bin2hex(random_bytes(32));
|
||||
|
||||
$fields = [
|
||||
'application-id' => $application['id'],
|
||||
'uid' => $uid,
|
||||
'code' => $code,
|
||||
'access_token' => $access_token,
|
||||
'scopes' => $scope,
|
||||
'read' => (stripos($scope, BaseApi::SCOPE_READ) !== false),
|
||||
'write' => (stripos($scope, BaseApi::SCOPE_WRITE) !== false),
|
||||
'follow' => (stripos($scope, BaseApi::SCOPE_FOLLOW) !== false),
|
||||
'push' => (stripos($scope, BaseApi::SCOPE_PUSH) !== false),
|
||||
'created_at' => DateTimeFormat::utcNow(DateTimeFormat::MYSQL)];
|
||||
|
||||
foreach ([BaseApi::SCOPE_READ, BaseApi::SCOPE_WRITE, BaseApi::SCOPE_WRITE, BaseApi::SCOPE_PUSH] as $scope) {
|
||||
if ($fields[$scope] && !$application[$scope]) {
|
||||
Logger::warning('Requested token scope is not allowed for the application', ['token' => $fields, 'application' => $application]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!DBA::insert('application-token', $fields, Database::INSERT_UPDATE)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return DBA::selectFirst('application-token', [], ['application-id' => $application['id'], 'uid' => $uid]);
|
||||
}
|
||||
}
|
||||
|
|
@ -31,9 +31,10 @@ use Exception;
|
|||
*/
|
||||
class DateTimeFormat
|
||||
{
|
||||
const ATOM = 'Y-m-d\TH:i:s\Z';
|
||||
const ATOM = 'Y-m-d\TH:i:s\Z';
|
||||
const MYSQL = 'Y-m-d H:i:s';
|
||||
const HTTP = 'D, d M Y H:i:s \G\M\T';
|
||||
const HTTP = 'D, d M Y H:i:s \G\M\T';
|
||||
const JSON = 'Y-m-d\TH:i:s.v\Z';
|
||||
|
||||
/**
|
||||
* convert() shorthand for UTC.
|
||||
|
|
|
|||
295
src/Util/HTTPInputData.php
Normal file
295
src/Util/HTTPInputData.php
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2021, the Friendica project
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Util;
|
||||
|
||||
/**
|
||||
* Derived from the work of Reid Johnson <https://codereview.stackexchange.com/users/4020/reid-johnson>
|
||||
* @see https://codereview.stackexchange.com/questions/69882/parsing-multipart-form-data-in-php-for-put-requests
|
||||
*/
|
||||
class HTTPInputData
|
||||
{
|
||||
public static function process()
|
||||
{
|
||||
$content_parts = explode(';', static::getContentType());
|
||||
|
||||
$boundary = '';
|
||||
$encoding = '';
|
||||
|
||||
$content_type = array_shift($content_parts);
|
||||
|
||||
foreach ($content_parts as $part) {
|
||||
if (strpos($part, 'boundary') !== false) {
|
||||
$part = explode('=', $part, 2);
|
||||
if (!empty($part[1])) {
|
||||
$boundary = '--' . $part[1];
|
||||
}
|
||||
} elseif (strpos($part, 'charset') !== false) {
|
||||
$part = explode('=', $part, 2);
|
||||
if (!empty($part[1])) {
|
||||
$encoding = $part[1];
|
||||
}
|
||||
}
|
||||
if ($boundary !== '' && $encoding !== '') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($content_type == 'multipart/form-data') {
|
||||
return self::fetchFromMultipart($boundary);
|
||||
}
|
||||
|
||||
// can be handled by built in PHP functionality
|
||||
$content = static::getPhpInputContent();
|
||||
|
||||
$variables = json_decode($content, true);
|
||||
|
||||
if (empty($variables)) {
|
||||
parse_str($content, $variables);
|
||||
}
|
||||
|
||||
return ['variables' => $variables, 'files' => []];
|
||||
}
|
||||
|
||||
private static function fetchFromMultipart(string $boundary)
|
||||
{
|
||||
$result = ['variables' => [], 'files' => []];
|
||||
|
||||
$stream = static::getPhpInputStream();
|
||||
|
||||
$sanity = fgets($stream, strlen($boundary) + 5);
|
||||
|
||||
// malformed file, boundary should be first item
|
||||
if (rtrim($sanity) !== $boundary) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$raw_headers = '';
|
||||
|
||||
while (($chunk = fgets($stream)) !== false) {
|
||||
if ($chunk === $boundary) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!empty(trim($chunk))) {
|
||||
$raw_headers .= $chunk;
|
||||
continue;
|
||||
}
|
||||
|
||||
$result = self::parseRawHeader($stream, $raw_headers, $boundary, $result);
|
||||
|
||||
$raw_headers = '';
|
||||
}
|
||||
|
||||
fclose($stream);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private static function parseRawHeader($stream, string $raw_headers, string $boundary, array $result)
|
||||
{
|
||||
$variables = $result['variables'];
|
||||
$files = $result['files'];
|
||||
|
||||
$headers = [];
|
||||
|
||||
foreach (explode("\r\n", $raw_headers) as $header) {
|
||||
if (strpos($header, ':') === false) {
|
||||
continue;
|
||||
}
|
||||
list($name, $value) = explode(':', $header, 2);
|
||||
|
||||
$headers[strtolower($name)] = ltrim($value, ' ');
|
||||
}
|
||||
|
||||
if (!isset($headers['content-disposition'])) {
|
||||
return ['variables' => $variables, 'files' => $files];
|
||||
}
|
||||
|
||||
if (!preg_match('/^(.+); *name="([^"]+)"(; *filename="([^"]+)")?/', $headers['content-disposition'], $matches)) {
|
||||
return ['variables' => $variables, 'files' => $files];
|
||||
}
|
||||
|
||||
$name = $matches[2];
|
||||
$filename = $matches[4] ?? '';
|
||||
|
||||
if (!empty($filename)) {
|
||||
$files[$name] = static::fetchFileData($stream, $boundary, $headers, $filename);
|
||||
return ['variables' => $variables, 'files' => $files];
|
||||
} else {
|
||||
$variables = self::fetchVariables($stream, $boundary, $headers, $name, $variables);
|
||||
}
|
||||
|
||||
return ['variables' => $variables, 'files' => $files];
|
||||
}
|
||||
|
||||
protected static function fetchFileData($stream, string $boundary, array $headers, string $filename)
|
||||
{
|
||||
$error = UPLOAD_ERR_OK;
|
||||
|
||||
if (isset($headers['content-type'])) {
|
||||
$tmp = explode(';', $headers['content-type']);
|
||||
|
||||
$contentType = $tmp[0];
|
||||
} else {
|
||||
$contentType = 'unknown';
|
||||
}
|
||||
|
||||
$tmpnam = tempnam(ini_get('upload_tmp_dir'), 'php');
|
||||
$fileHandle = fopen($tmpnam, 'wb');
|
||||
|
||||
if ($fileHandle === false) {
|
||||
$error = UPLOAD_ERR_CANT_WRITE;
|
||||
} else {
|
||||
$lastLine = null;
|
||||
while (($chunk = fgets($stream, 8096)) !== false && strpos($chunk, $boundary) !== 0) {
|
||||
if ($lastLine !== null) {
|
||||
if (!fwrite($fileHandle, $lastLine)) {
|
||||
$error = UPLOAD_ERR_CANT_WRITE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$lastLine = $chunk;
|
||||
}
|
||||
|
||||
if ($lastLine !== null && $error !== UPLOAD_ERR_CANT_WRITE) {
|
||||
if (!fwrite($fileHandle, rtrim($lastLine, "\r\n"))) {
|
||||
$error = UPLOAD_ERR_CANT_WRITE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'name' => $filename,
|
||||
'type' => $contentType,
|
||||
'tmp_name' => $tmpnam,
|
||||
'error' => $error,
|
||||
'size' => filesize($tmpnam)
|
||||
];
|
||||
}
|
||||
|
||||
private static function fetchVariables($stream, string $boundary, array $headers, string $name, array $variables)
|
||||
{
|
||||
$fullValue = '';
|
||||
$lastLine = null;
|
||||
|
||||
while (($chunk = fgets($stream)) !== false && strpos($chunk, $boundary) !== 0) {
|
||||
if ($lastLine !== null) {
|
||||
$fullValue .= $lastLine;
|
||||
}
|
||||
|
||||
$lastLine = $chunk;
|
||||
}
|
||||
|
||||
if ($lastLine !== null) {
|
||||
$fullValue .= rtrim($lastLine, "\r\n");
|
||||
}
|
||||
|
||||
if (isset($headers['content-type'])) {
|
||||
$encoding = '';
|
||||
|
||||
foreach (explode(';', $headers['content-type']) as $part) {
|
||||
if (strpos($part, 'charset') !== false) {
|
||||
$part = explode($part, '=', 2);
|
||||
if (isset($part[1])) {
|
||||
$encoding = $part[1];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($encoding !== '' && strtoupper($encoding) !== 'UTF-8' && strtoupper($encoding) !== 'UTF8') {
|
||||
$tmp = mb_convert_encoding($fullValue, 'UTF-8', $encoding);
|
||||
if ($tmp !== false) {
|
||||
$fullValue = $tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$fullValue = $name . '=' . $fullValue;
|
||||
|
||||
$tmp = [];
|
||||
parse_str($fullValue, $tmp);
|
||||
|
||||
return self::expandVariables(explode('[', $name), $variables, $tmp);
|
||||
}
|
||||
|
||||
private static function expandVariables(array $names, $variables, array $values)
|
||||
{
|
||||
if (!is_array($variables)) {
|
||||
return $values;
|
||||
}
|
||||
|
||||
$name = rtrim(array_shift($names), ']');
|
||||
if ($name !== '') {
|
||||
$name = $name . '=p';
|
||||
|
||||
$tmp = [];
|
||||
parse_str($name, $tmp);
|
||||
|
||||
$tmp = array_keys($tmp);
|
||||
$name = reset($tmp);
|
||||
}
|
||||
|
||||
if ($name === '') {
|
||||
$variables[] = reset($values);
|
||||
} elseif (isset($variables[$name]) && isset($values[$name])) {
|
||||
$variables[$name] = self::expandVariables($names, $variables[$name], $values[$name]);
|
||||
} elseif (isset($values[$name])) {
|
||||
$variables[$name] = $values[$name];
|
||||
}
|
||||
|
||||
return $variables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current PHP input stream
|
||||
* Mainly used for test doubling
|
||||
*
|
||||
* @return false|resource
|
||||
*/
|
||||
protected static function getPhpInputStream()
|
||||
{
|
||||
return fopen('php://input', 'rb');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content of the current PHP input
|
||||
* Mainly used for test doubling
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
protected static function getPhpInputContent()
|
||||
{
|
||||
return file_get_contents('php://input');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content type string of the current call
|
||||
* Mainly used for test doubling
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
protected static function getContentType()
|
||||
{
|
||||
return $_SERVER['CONTENT_TYPE'] ?? 'application/x-www-form-urlencoded';
|
||||
}
|
||||
}
|
||||
|
|
@ -558,8 +558,10 @@ class HTTPSignature
|
|||
if (!empty($key['url']) && !empty($key['type']) && ($key['type'] == 'Tombstone')) {
|
||||
Logger::info('Actor is a tombstone', ['key' => $key]);
|
||||
|
||||
// We now delete everything that we possibly knew from this actor
|
||||
Contact::deleteContactByUrl($key['url']);
|
||||
if (!Contact::isLocal($key['url'])) {
|
||||
// We now delete everything that we possibly knew from this actor
|
||||
Contact::deleteContactByUrl($key['url']);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -638,16 +640,17 @@ class HTTPSignature
|
|||
|
||||
$profile = APContact::getByURL($url);
|
||||
if (!empty($profile)) {
|
||||
Logger::log('Taking key from id ' . $id, Logger::DEBUG);
|
||||
Logger::info('Taking key from id', ['id' => $id]);
|
||||
return ['url' => $url, 'pubkey' => $profile['pubkey'], 'type' => $profile['type']];
|
||||
} elseif ($url != $actor) {
|
||||
$profile = APContact::getByURL($actor);
|
||||
if (!empty($profile)) {
|
||||
Logger::log('Taking key from actor ' . $actor, Logger::DEBUG);
|
||||
Logger::info('Taking key from actor', ['actor' => $actor]);
|
||||
return ['url' => $actor, 'pubkey' => $profile['pubkey'], 'type' => $profile['type']];
|
||||
}
|
||||
}
|
||||
|
||||
Logger::notice('Key could not be fetched', ['url' => $url, 'actor' => $actor]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -548,4 +548,15 @@ class Network
|
|||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given URL is a local link
|
||||
*
|
||||
* @param string $url
|
||||
* @return bool
|
||||
*/
|
||||
public static function isLocalLink(string $url)
|
||||
{
|
||||
return (strpos(Strings::normaliseLink($url), Strings::normaliseLink(DI::baseUrl())) !== false);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,11 +37,20 @@ class Proxy
|
|||
/**
|
||||
* Sizes constants
|
||||
*/
|
||||
const SIZE_MICRO = 'micro';
|
||||
const SIZE_THUMB = 'thumb';
|
||||
const SIZE_SMALL = 'small';
|
||||
const SIZE_MEDIUM = 'medium';
|
||||
const SIZE_LARGE = 'large';
|
||||
const SIZE_MICRO = 'micro'; // 48
|
||||
const SIZE_THUMB = 'thumb'; // 80
|
||||
const SIZE_SMALL = 'small'; // 300
|
||||
const SIZE_MEDIUM = 'medium'; // 600
|
||||
const SIZE_LARGE = 'large'; // 1024
|
||||
|
||||
/**
|
||||
* Pixel Sizes
|
||||
*/
|
||||
const PIXEL_MICRO = 48;
|
||||
const PIXEL_THUMB = 80;
|
||||
const PIXEL_SMALL = 300;
|
||||
const PIXEL_MEDIUM = 600;
|
||||
const PIXEL_LARGE = 1024;
|
||||
|
||||
/**
|
||||
* Accepted extensions
|
||||
|
|
|
|||
|
|
@ -30,13 +30,11 @@ use Friendica\Protocol\DFRN;
|
|||
use Friendica\Protocol\Diaspora;
|
||||
use Friendica\Protocol\Email;
|
||||
use Friendica\Protocol\Activity;
|
||||
use Friendica\Util\Strings;
|
||||
use Friendica\Util\Network;
|
||||
use Friendica\Core\Worker;
|
||||
use Friendica\Model\Conversation;
|
||||
use Friendica\Model\FContact;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Protocol\Relay;
|
||||
|
||||
class Delivery
|
||||
|
|
@ -216,11 +214,6 @@ class Delivery
|
|||
$contact['network'] = Protocol::DIASPORA;
|
||||
}
|
||||
|
||||
// Ensure that local contacts are delivered locally
|
||||
if (Model\Contact::isLocal($contact['url'])) {
|
||||
$contact['network'] = Protocol::DFRN;
|
||||
}
|
||||
|
||||
Logger::notice('Delivering', ['cmd' => $cmd, 'uri-id' => $post_uriid, 'followup' => $followup, 'network' => $contact['network']]);
|
||||
|
||||
switch ($contact['network']) {
|
||||
|
|
@ -316,40 +309,6 @@ class Delivery
|
|||
|
||||
Logger::debug('Notifier entry: ' . $contact["url"] . ' ' . (($target_item['guid'] ?? '') ?: $target_item['id']) . ' entry: ' . $atom);
|
||||
|
||||
// perform local delivery if we are on the same site
|
||||
if (Model\Contact::isLocal($contact['url'])) {
|
||||
$condition = ['nurl' => Strings::normaliseLink($contact['url']), 'self' => true];
|
||||
$target_self = DBA::selectFirst('contact', ['uid'], $condition);
|
||||
if (!DBA::isResult($target_self)) {
|
||||
return;
|
||||
}
|
||||
$target_uid = $target_self['uid'];
|
||||
|
||||
// Check if the user has got this contact
|
||||
$cid = Model\Contact::getIdForURL($owner['url'], $target_uid);
|
||||
if (!$cid) {
|
||||
// Otherwise there should be a public contact
|
||||
$cid = Model\Contact::getIdForURL($owner['url']);
|
||||
if (!$cid) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$target_importer = DFRN::getImporter($cid, $target_uid);
|
||||
if (empty($target_importer)) {
|
||||
// This should never happen
|
||||
return;
|
||||
}
|
||||
|
||||
DFRN::import($atom, $target_importer, Conversation::PARCEL_LOCAL_DFRN, Conversation::PUSH);
|
||||
|
||||
if (in_array($cmd, [Delivery::POST, Delivery::POKE])) {
|
||||
Model\Post\DeliveryData::incrementQueueDone($target_item['uri-id'], Model\Post\DeliveryData::DFRN);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$protocol = Model\Post\DeliveryData::DFRN;
|
||||
|
||||
// We don't have a relationship with contacts on a public post.
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ class ExpirePosts
|
|||
Logger::notice('Adding missing entries');
|
||||
|
||||
$rows = 0;
|
||||
$userposts = DBA::select('post-user', [], ["`uri-id` not in (select `uri-id` from `post`)"], ['group_by' => ['uri-id']]);
|
||||
$userposts = DBA::select('post-user', [], ["`uri-id` not in (select `uri-id` from `post`)"]);
|
||||
while ($fields = DBA::fetch($userposts)) {
|
||||
$post_fields = DBStructure::getFieldsForTable('post', $fields);
|
||||
DBA::insert('post', $post_fields, Database::INSERT_IGNORE);
|
||||
|
|
@ -133,7 +133,7 @@ class ExpirePosts
|
|||
}
|
||||
|
||||
$rows = 0;
|
||||
$userposts = DBA::select('post-user', [], ["`gravity` = ? AND `uri-id` not in (select `uri-id` from `post-thread`)", GRAVITY_PARENT], ['group_by' => ['uri-id']]);
|
||||
$userposts = DBA::select('post-user', [], ["`gravity` = ? AND `uri-id` not in (select `uri-id` from `post-thread`)", GRAVITY_PARENT]);
|
||||
while ($fields = DBA::fetch($userposts)) {
|
||||
$post_fields = DBStructure::getFieldsForTable('post-thread', $fields);
|
||||
$post_fields['commented'] = $post_fields['changed'] = $post_fields['created'];
|
||||
|
|
@ -181,7 +181,10 @@ class ExpirePosts
|
|||
AND NOT EXISTS(SELECT `uri-id` FROM `post-user` WHERE `uri-id` = `item-uri`.`id`)
|
||||
AND NOT EXISTS(SELECT `parent-uri-id` FROM `post-user` WHERE `parent-uri-id` = `item-uri`.`id`)
|
||||
AND NOT EXISTS(SELECT `thr-parent-id` FROM `post-user` WHERE `thr-parent-id` = `item-uri`.`id`)
|
||||
AND NOT EXISTS(SELECT `external-id` FROM `post-user` WHERE `external-id` = `item-uri`.`id`)", $item['uri-id']]);
|
||||
AND NOT EXISTS(SELECT `external-id` FROM `post-user` WHERE `external-id` = `item-uri`.`id`)
|
||||
AND NOT EXISTS(SELECT `uri-id` FROM `mail` WHERE `uri-id` = `item-uri`.`id`)
|
||||
AND NOT EXISTS(SELECT `parent-uri-id` FROM `mail` WHERE `parent-uri-id` = `item-uri`.`id`)
|
||||
AND NOT EXISTS(SELECT `thr-parent-id` FROM `mail` WHERE `thr-parent-id` = `item-uri`.`id`)", $item['uri-id']]);
|
||||
|
||||
Logger::notice('Start deleting orphaned URI-ID', ['last-id' => $item['uri-id']]);
|
||||
$affected_count = 0;
|
||||
|
|
|
|||
|
|
@ -39,8 +39,9 @@ use Friendica\Protocol\Activity;
|
|||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Protocol\Diaspora;
|
||||
use Friendica\Protocol\OStatus;
|
||||
use Friendica\Protocol\Relay;
|
||||
use Friendica\Protocol\Salmon;
|
||||
use Friendica\Util\Network;
|
||||
use Friendica\Util\Strings;
|
||||
|
||||
/*
|
||||
* The notifier is typically called with:
|
||||
|
|
@ -248,6 +249,20 @@ class Notifier
|
|||
$direct_forum_delivery = true;
|
||||
}
|
||||
|
||||
$exclusive_delivery = false;
|
||||
|
||||
$exclusive_targets = Tag::getByURIId($parent['uri-id'], [Tag::EXCLUSIVE_MENTION]);
|
||||
if (!empty($exclusive_targets)) {
|
||||
$exclusive_delivery = true;
|
||||
Logger::info('Possible Exclusively delivering', ['uid' => $target_item['uid'], 'guid' => $target_item['guid'], 'uri-id' => $target_item['uri-id']]);
|
||||
foreach ($exclusive_targets as $target) {
|
||||
if (Strings::compareLink($owner['url'], $target['url'])) {
|
||||
$exclusive_delivery = false;
|
||||
Logger::info('False Exclusively delivering', ['uid' => $target_item['uid'], 'guid' => $target_item['guid'], 'uri-id' => $target_item['uri-id'], 'url' => $target['url']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($relay_to_owner) {
|
||||
// local followup to remote post
|
||||
$followup = true;
|
||||
|
|
@ -282,6 +297,16 @@ class Notifier
|
|||
}
|
||||
|
||||
Logger::log('Notify ' . $target_item["guid"] .' via PuSH: ' . ($push_notify ? "Yes":"No"), Logger::DEBUG);
|
||||
} elseif ($exclusive_delivery) {
|
||||
$followup = true;
|
||||
|
||||
foreach ($exclusive_targets as $target) {
|
||||
$cid = Contact::getIdForURL($target['url'], $uid, false);
|
||||
if ($cid) {
|
||||
$recipients_followup[] = $cid;
|
||||
Logger::info('Exclusively delivering', ['uid' => $target_item['uid'], 'guid' => $target_item['guid'], 'uri-id' => $target_item['uri-id'], 'url' => $target['url']]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$followup = false;
|
||||
|
||||
|
|
@ -313,7 +338,7 @@ class Notifier
|
|||
/// @todo Possibly we should not uplink when the author is the forum itself?
|
||||
|
||||
if ((intval($parent['forum_mode']) == 1) && !$top_level && ($cmd !== Delivery::UPLINK)
|
||||
&& ($target_item['verb'] != Activity::ANNOUNCE)) {
|
||||
&& ($target_item['verb'] != Activity::ANNOUNCE)) {
|
||||
Worker::add($a->queue['priority'], 'Notifier', Delivery::UPLINK, $post_uriid, $sender_uid);
|
||||
}
|
||||
|
||||
|
|
@ -416,12 +441,12 @@ class Notifier
|
|||
$batch_delivery = false;
|
||||
|
||||
if ($public_message && !in_array($cmd, [Delivery::MAIL, Delivery::SUGGESTION]) && !$followup) {
|
||||
$relay_list = [];
|
||||
$participants = [];
|
||||
|
||||
if ($diaspora_delivery && !$unlisted) {
|
||||
$batch_delivery = true;
|
||||
|
||||
$relay_list_stmt = DBA::p(
|
||||
$participants_stmt = DBA::p(
|
||||
"SELECT
|
||||
`batch`, `network`, `protocol`,
|
||||
ANY_VALUE(`id`) AS `id`,
|
||||
|
|
@ -440,17 +465,11 @@ class Notifier
|
|||
$owner['uid'],
|
||||
Contact::SHARING
|
||||
);
|
||||
$relay_list = DBA::toArray($relay_list_stmt);
|
||||
$participants = DBA::toArray($participants_stmt);
|
||||
|
||||
// Fetch the participation list
|
||||
// The function will ensure that there are no duplicates
|
||||
$relay_list = Diaspora::participantsForThread($target_item, $relay_list);
|
||||
|
||||
// Add the relay to the list, avoid duplicates.
|
||||
// Don't send community posts to the relay. Forum posts via the Diaspora protocol are looking ugly.
|
||||
if (!$followup && !Item::isForumPost($target_item, $owner) && !self::isForumPost($target_item)) {
|
||||
$relay_list = Relay::getList($target_id, $relay_list, [Protocol::DFRN, Protocol::DIASPORA]);
|
||||
}
|
||||
$participants = Diaspora::participantsForThread($target_item, $participants);
|
||||
}
|
||||
|
||||
$condition = ['network' => Protocol::DFRN, 'uid' => $owner['uid'], 'blocked' => false,
|
||||
|
|
@ -458,7 +477,7 @@ class Notifier
|
|||
|
||||
$contacts = DBA::toArray(DBA::select('contact', ['id', 'url', 'addr', 'name', 'network', 'protocol'], $condition));
|
||||
|
||||
$conversants = array_merge($contacts, $relay_list);
|
||||
$conversants = array_merge($contacts, $participants);
|
||||
|
||||
$delivery_queue_count += self::delivery($cmd, $post_uriid, $sender_uid, $target_item, $thr_parent, $owner, $batch_delivery, true, $conversants, $ap_contacts, []);
|
||||
|
||||
|
|
@ -494,29 +513,32 @@ class Notifier
|
|||
/**
|
||||
* Deliver the message to the contacts
|
||||
*
|
||||
* @param string $cmd
|
||||
* @param int $post_uriid
|
||||
* @param string $cmd
|
||||
* @param int $post_uriid
|
||||
* @param int $sender_uid
|
||||
* @param array $target_item
|
||||
* @param array $thr_parent
|
||||
* @param array $owner
|
||||
* @param bool $batch_delivery
|
||||
* @param array $contacts
|
||||
* @param array $ap_contacts
|
||||
* @param array $conversants
|
||||
* @return int
|
||||
* @throws InternalServerErrorException
|
||||
* @throws Exception
|
||||
* @param array $target_item
|
||||
* @param array $thr_parent
|
||||
* @param array $owner
|
||||
* @param bool $batch_delivery
|
||||
* @param array $contacts
|
||||
* @param array $ap_contacts
|
||||
* @param array $conversants
|
||||
* @return int
|
||||
* @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 = [])
|
||||
{
|
||||
$a = DI::app();
|
||||
$a = DI::app();
|
||||
$delivery_queue_count = 0;
|
||||
|
||||
foreach ($contacts as $contact) {
|
||||
// Ensure that local contacts are delivered via DFRN
|
||||
if (Contact::isLocal($contact['url'])) {
|
||||
$contact['network'] = Protocol::DFRN;
|
||||
// Direct delivery of local contacts
|
||||
if ($target_uid = User::getIdForURL($contact['url'])) {
|
||||
Logger::info('Direct delivery', ['uri-id' => $target_item['uri-id'], 'target' => $target_uid]);
|
||||
$fields = ['protocol' => Conversation::PARCEL_LOCAL_DFRN, 'direction' => Conversation::PUSH];
|
||||
Item::storeForUserByUriId($target_item['uri-id'], $target_uid, $fields, $target_item['uid']);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Deletions are always sent via DFRN as well.
|
||||
|
|
@ -575,19 +597,19 @@ class Notifier
|
|||
/**
|
||||
* Deliver the message via OStatus
|
||||
*
|
||||
* @param int $target_id
|
||||
* @param array $target_item
|
||||
* @param array $owner
|
||||
* @param array $url_recipients
|
||||
* @param bool $public_message
|
||||
* @param bool $push_notify
|
||||
* @return int
|
||||
* @throws InternalServerErrorException
|
||||
* @throws Exception
|
||||
* @param int $target_id
|
||||
* @param array $target_item
|
||||
* @param array $owner
|
||||
* @param array $url_recipients
|
||||
* @param bool $public_message
|
||||
* @param bool $push_notify
|
||||
* @return int
|
||||
* @throws InternalServerErrorException
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function deliverOStatus(int $target_id, array $target_item, array $owner, array $url_recipients, bool $public_message, bool $push_notify)
|
||||
{
|
||||
$a = DI::app();
|
||||
$a = DI::app();
|
||||
$delivery_queue_count = 0;
|
||||
|
||||
$url_recipients = array_filter($url_recipients);
|
||||
|
|
@ -775,6 +797,16 @@ class Notifier
|
|||
foreach ($inboxes as $inbox => $receivers) {
|
||||
$contacts = array_merge($contacts, $receivers);
|
||||
|
||||
if ((count($receivers) == 1) && Network::isLocalLink($inbox)) {
|
||||
$contact = Contact::getById($receivers[0], ['url']);
|
||||
if ($target_uid = User::getIdForURL($contact['url'])) {
|
||||
$fields = ['protocol' => Conversation::PARCEL_LOCAL_DFRN, 'direction' => Conversation::PUSH, 'post-reason' => Item::PR_BCC];
|
||||
Item::storeForUserByUriId($target_item['uri-id'], $target_uid, $fields, $target_item['uid']);
|
||||
Logger::info('Delivered locally', ['cmd' => $cmd, 'id' => $target_item['id'], 'inbox' => $inbox]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Logger::info('Delivery via ActivityPub', ['cmd' => $cmd, 'id' => $target_item['id'], 'inbox' => $inbox]);
|
||||
|
||||
if (Worker::add(['priority' => $priority, 'created' => $created, 'dont_fork' => true],
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ class RemoveContact {
|
|||
}
|
||||
|
||||
DBA::delete('mail', ['contact-id' => $id]);
|
||||
DBA::delete('mail', ['author-id' => $id]);
|
||||
|
||||
Post\ThreadUser::delete(['author-id' => $id]);
|
||||
Post\ThreadUser::delete(['owner-id' => $id]);
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@
|
|||
namespace Friendica\Worker;
|
||||
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Core\Worker;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Photo;
|
||||
|
||||
/**
|
||||
|
|
@ -56,5 +56,73 @@ class RemoveUnusedAvatars
|
|||
}
|
||||
DBA::close($contacts);
|
||||
Logger::notice('Removal done', ['count' => $count, 'total' => $total]);
|
||||
|
||||
self::fixPhotoContacts();
|
||||
self::deleteDuplicates();
|
||||
}
|
||||
|
||||
private static function fixPhotoContacts()
|
||||
{
|
||||
$total = 0;
|
||||
$deleted = 0;
|
||||
$updated1 = 0;
|
||||
$updated2 = 0;
|
||||
Logger::notice('Starting contact fix');
|
||||
$photos = DBA::select('photo', [], ["`uid` = ? AND `contact-id` IN (SELECT `id` FROM `contact` WHERE `uid` != ?) AND `contact-id` != ? AND `scale` IN (?, ?, ?)", 0, 0, 0, 4, 5, 6]);
|
||||
while ($photo = DBA::fetch($photos)) {
|
||||
$total++;
|
||||
$photo_contact = Contact::getById($photo['contact-id']);
|
||||
$resource = Photo::ridFromURI($photo_contact['photo']);
|
||||
if ($photo['resource-id'] == $resource) {
|
||||
$contact = DBA::selectFirst('contact', [], ['nurl' => $photo_contact['nurl'], 'uid' => 0]);
|
||||
if (!empty($contact['photo']) && ($contact['photo'] == $photo_contact['photo'])) {
|
||||
Logger::notice('Photo updated to public user', ['id' => $photo['id'], 'contact-id' => $contact['id']]);
|
||||
DBA::update('photo', ['contact-id' => $contact['id']], ['id' => $photo['id']]);
|
||||
$updated1++;
|
||||
}
|
||||
} else {
|
||||
$updated = false;
|
||||
$contacts = DBA::select('contact', [], ['nurl' => $photo_contact['nurl']]);
|
||||
while ($contact = DBA::fetch($contacts)) {
|
||||
if ($photo['resource-id'] == Photo::ridFromURI($contact['photo'])) {
|
||||
Logger::notice('Photo updated to given user', ['id' => $photo['id'], 'contact-id' => $contact['id'], 'uid' => $contact['uid']]);
|
||||
DBA::update('photo', ['contact-id' => $contact['id'], 'uid' => $contact['uid']], ['id' => $photo['id']]);
|
||||
$updated = true;
|
||||
$updated2++;
|
||||
}
|
||||
}
|
||||
DBA::close($contacts);
|
||||
if (!$updated) {
|
||||
Logger::notice('Photo deleted', ['id' => $photo['id']]);
|
||||
Photo::delete(['id' => $photo['id']]);
|
||||
$deleted++;
|
||||
}
|
||||
}
|
||||
}
|
||||
DBA::close($photos);
|
||||
Logger::notice('Contact fix done', ['total' => $total, 'updated1' => $updated1, 'updated2' => $updated2, 'deleted' => $deleted]);
|
||||
}
|
||||
|
||||
private static function deleteDuplicates()
|
||||
{
|
||||
$size = [4 => 'photo', 5 => 'thumb', 6 => 'micro'];
|
||||
|
||||
$total = 0;
|
||||
$deleted = 0;
|
||||
Logger::notice('Starting duplicate removal');
|
||||
$photos = DBA::p("SELECT `photo`.`id`, `photo`.`uid`, `photo`.`scale`, `photo`.`album`, `photo`.`contact-id`, `photo`.`resource-id`, `contact`.`photo`, `contact`.`thumb`, `contact`.`micro` FROM `photo` INNER JOIN `contact` ON `contact`.`id` = `photo`.`contact-id` and `photo`.`contact-id` != ? AND `photo`.`scale` IN (?, ?, ?)", 0, 4, 5, 6);
|
||||
while ($photo = DBA::fetch($photos)) {
|
||||
$resource = Photo::ridFromURI($photo[$size[$photo['scale']]]);
|
||||
if ($resource != $photo['resource-id'] && !empty($resource)) {
|
||||
$total++;
|
||||
if (DBA::exists('photo', ['resource-id' => $resource, 'scale' => $photo['scale']])) {
|
||||
Logger::notice('Photo deleted', ['id' => $photo['id']]);
|
||||
Photo::delete(['id' => $photo['id']]);
|
||||
$deleted++;
|
||||
}
|
||||
}
|
||||
}
|
||||
DBA::close($photos);
|
||||
Logger::notice('Duplicate removal done', ['total' => $total, 'deleted' => $deleted]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue