Merge branch 'stable' into develop

This commit is contained in:
Tobias Diekershoff 2021-07-04 20:52:22 +02:00
commit 2b95a7e7cd
323 changed files with 21146 additions and 16271 deletions

View file

@ -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);

View file

@ -232,7 +232,7 @@ class BaseURL
{
$parsed = @parse_url($url);
if (empty($parsed)) {
if (empty($parsed) || empty($parsed['host'])) {
return false;
}

View file

@ -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');

View file

@ -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 */

View 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();
}
}

View file

@ -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;
}

View file

@ -120,6 +120,7 @@ HELP;
$output = ob_get_clean();
break;
case "dumpsql":
DBStructure::writeStructure();
ob_start();
DBStructure::printStructure($basePath);
$output = ob_get_clean();

View file

@ -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;
}

View file

@ -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('&#x2672; ', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ': </p>' . "\n" . $content;
$text = ($is_quote_share? '<br>' : '') .
'<p><b><a href="' . $attributes['link'] . '">' . html_entity_decode('&#x2672; ', 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;
}
}

View file

@ -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;

View file

@ -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);

View file

@ -172,6 +172,8 @@ HELP;
Friendica\DI::init($this->dice);
Renderer::registerTemplateEngine('Friendica\Render\FriendicaSmartyEngine');
/** @var Console $subconsole */
$subconsole = $this->dice->create($className, [$subargs]);

View file

@ -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;

View file

@ -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);
}
/**

View file

@ -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()]));
}
/**

View file

@ -213,6 +213,8 @@ class Update
if ($sendMail) {
self::updateSuccessful($stored, $current);
}
} else {
Logger::warning('Update lock could not be acquired');
}
}
}

View file

@ -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
*/

View file

@ -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`

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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 = '';
}

View file

@ -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'])) {

View file

@ -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);

View file

@ -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);
}

View file

@ -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());
}
}

View file

@ -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 = [];

View file

@ -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);
}
}

View file

@ -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');
}
}

View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -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)) {

View file

@ -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);

View file

@ -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;

View file

@ -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']),

View file

@ -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'),

View file

@ -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']]) ?: [];
}
/**

View file

@ -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

View file

@ -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)) {

View file

@ -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);

View file

@ -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'])) {

View file

@ -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']);

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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;
}
}

View file

@ -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

View file

@ -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.
*

View 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;
}
}

View file

@ -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
{

View file

@ -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]);

View file

@ -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
*

View file

@ -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".')],

View file

@ -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'];

View file

@ -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);

View file

@ -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,

View file

@ -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);
}
}

View file

@ -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'])) {

View file

@ -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());
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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([]);
}

View file

@ -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'])) {

View file

@ -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'])) {

View file

@ -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'])) {

View file

@ -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) {

View file

@ -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([

View file

@ -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);
}
}

View file

@ -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'])) {

View file

@ -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'])) {

View file

@ -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'])) {

View file

@ -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');
}
}

View file

@ -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);

View file

@ -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([]);

View file

@ -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());
}
}

View file

@ -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']));
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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'])) {

View file

@ -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);
}
}

View file

@ -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([]);
}
}

View file

@ -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);
}
}

View 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);
}
}

View file

@ -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'])) {

View file

@ -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);
}
}

View file

@ -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([]);
}

View file

@ -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'])) {

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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([]);
}

View file

@ -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([]);
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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));
}
}

View file

@ -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'])) {

View file

@ -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);

View file

@ -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'])) {

View file

@ -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 = [];

View file

@ -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'])) {

View file

@ -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'])) {

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