Merge remote-tracking branch 'origin/2022.12-rc' into fixes

This commit is contained in:
Michael 2022-12-11 19:00:59 +00:00
commit 2f3f41ed9c
714 changed files with 34811 additions and 27839 deletions

View file

@ -28,6 +28,7 @@ use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Item;
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
use Friendica\Network\HTTPException;
use Friendica\Network\Probe;
@ -61,7 +62,7 @@ class APContact
'addr' => $local_owner['addr'],
'baseurl' => $local_owner['baseurl'],
'url' => $local_owner['url'],
'subscribe' => $local_owner['baseurl'] . '/follow?url={uri}'];
'subscribe' => $local_owner['baseurl'] . '/contact/follow?url={uri}'];
if (!empty($local_owner['alias']) && ($local_owner['url'] != $local_owner['alias'])) {
$data['alias'] = $local_owner['alias'];
@ -290,22 +291,19 @@ class APContact
return $fetched_contact;
}
$parts = parse_url($apcontact['url']);
unset($parts['scheme']);
unset($parts['path']);
if (empty($apcontact['addr'])) {
if (!empty($apcontact['nick']) && is_array($parts)) {
$apcontact['addr'] = $apcontact['nick'] . '@' . str_replace('//', '', Network::unparseURL($parts));
} else {
try {
$apcontact['addr'] = $apcontact['nick'] . '@' . (new Uri($apcontact['url']))->getAuthority();
} catch (\Throwable $e) {
Logger::warning('Unable to coerce APContact URL into a UriInterface object', ['url' => $apcontact['url'], 'error' => $e->getMessage()]);
$apcontact['addr'] = '';
}
}
$apcontact['pubkey'] = null;
if (!empty($compacted['w3id:publicKey'])) {
$apcontact['pubkey'] = trim(JsonLD::fetchElement($compacted['w3id:publicKey'], 'w3id:publicKeyPem', '@value'));
if (strstr($apcontact['pubkey'], 'RSA ')) {
$apcontact['pubkey'] = trim(JsonLD::fetchElement($compacted['w3id:publicKey'], 'w3id:publicKeyPem', '@value') ?? '');
if (strpos($apcontact['pubkey'], 'RSA ') !== false) {
$apcontact['pubkey'] = Crypto::rsaToPem($apcontact['pubkey']);
}
}
@ -382,7 +380,7 @@ class APContact
// kroeg:blocks, updated
// When the photo is too large, try to shorten it by removing parts
if (strlen($apcontact['photo']) > 255) {
if (strlen($apcontact['photo'] ?? '') > 255) {
$parts = parse_url($apcontact['photo']);
unset($parts['fragment']);
$apcontact['photo'] = (string)Uri::fromParts($parts);
@ -495,13 +493,13 @@ class APContact
private static function getStatusesCount(array $owner): int
{
$condition = [
'private' => [Item::PUBLIC, Item::UNLISTED],
'private' => [Item::PUBLIC, Item::UNLISTED],
'author-id' => Contact::getIdForURL($owner['url'], 0, false),
'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT],
'gravity' => [Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT],
'network' => Protocol::DFRN,
'parent-network' => Protocol::FEDERATED,
'deleted' => false,
'visible' => true
'visible' => true,
];
$count = Post::countPosts($condition);
@ -573,7 +571,7 @@ class APContact
*
* @param array $apcontact
*
* @return bool
* @return bool
*/
public static function isRelay(array $apcontact): bool
{

View file

@ -29,7 +29,6 @@ use Friendica\Core\Hook;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\Renderer;
use Friendica\Core\Session;
use Friendica\Core\System;
use Friendica\Core\Worker;
use Friendica\Database\Database;
@ -97,17 +96,17 @@ class Contact
* Relationship types
* @{
*/
const NOTHING = 0;
const FOLLOWER = 1;
const SHARING = 2;
const FRIEND = 3;
const SELF = 4;
const NOTHING = 0; // There is no relationship between the contact and the user
const FOLLOWER = 1; // The contact is following this user (the contact is the subscriber)
const SHARING = 2; // The contact shares their content with this user (the user is the subscriber)
const FRIEND = 3; // There is a mutual relationship between the contact and the user
const SELF = 4; // This is the user theirself
/**
* @}
*/
const MIRROR_DEACTIVATED = 0;
const MIRROR_FORWARDED = 1;
const MIRROR_FORWARDED = 1; // Deprecated, now does the same like MIRROR_OWN_POST
const MIRROR_OWN_POST = 2;
const MIRROR_NATIVE_RESHARE = 3;
@ -137,6 +136,18 @@ class Contact
return $contact;
}
/**
* @param array $fields Array of selected fields, empty for all
* @param array $condition Array of fields for condition
* @param array $params Array of several parameters
* @return array
* @throws \Exception
*/
public static function selectAccountToArray(array $fields = [], array $condition = [], array $params = []): array
{
return DBA::selectToArray('account-user-view', $fields, $condition, $params);
}
/**
* @param array $fields Array of selected fields, empty for all
* @param array $condition Array of fields for condition
@ -261,6 +272,32 @@ class Contact
return DBA::selectFirst('contact', $fields, ['uri-id' => $uri_id], ['order' => ['uid']]);
}
/**
* Fetch all remote contacts for a given contact url
*
* @param string $url The URL of the contact
* @param array $fields The wanted fields
*
* @return array all remote contacts
*
* @throws \Exception
*/
public static function getVisitorByUrl(string $url, array $fields = ['id', 'uid']): array
{
$remote = [];
$remote_contacts = DBA::select('contact', ['id', 'uid'], ['nurl' => Strings::normaliseLink($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;
}
$remote[$contact['uid']] = $contact['id'];
}
DBA::close($remote_contacts);
return $remote;
}
/**
* Fetches a contact by a given url
*
@ -319,7 +356,7 @@ class Contact
// Update the contact in the background if needed
if (Probe::isProbable($contact['network']) && ($contact['next-update'] < DateTimeFormat::utcNow())) {
Worker::add(['priority' => PRIORITY_LOW, 'dont_fork' => true], 'UpdateContact', $contact['id']);
Worker::add(['priority' => Worker::PRIORITY_LOW, 'dont_fork' => true], 'UpdateContact', $contact['id']);
}
// Remove the internal fields
@ -704,7 +741,6 @@ class Contact
'notify' => DI::baseUrl() . '/dfrn_notify/' . $user['nickname'],
'poll' => DI::baseUrl() . '/dfrn_poll/' . $user['nickname'],
'confirm' => DI::baseUrl() . '/dfrn_confirm/' . $user['nickname'],
'poco' => DI::baseUrl() . '/poco/' . $user['nickname'],
'name-date' => DateTimeFormat::utcNow(),
'uri-date' => DateTimeFormat::utcNow(),
'avatar-date' => DateTimeFormat::utcNow(),
@ -786,7 +822,6 @@ class Contact
'notify' => DI::baseUrl() . '/dfrn_notify/' . $user['nickname'],
'poll' => DI::baseUrl() . '/dfrn_poll/'. $user['nickname'],
'confirm' => DI::baseUrl() . '/dfrn_confirm/' . $user['nickname'],
'poco' => DI::baseUrl() . '/poco/' . $user['nickname'],
];
@ -877,14 +912,14 @@ class Contact
self::clearFollowerFollowingEndpointCache($contact['uid']);
// Archive the contact
self::update(['archive' => true, 'network' => Protocol::PHANTOM, 'deleted' => true], ['id' => $id]);
self::update(['archive' => true, 'network' => Protocol::PHANTOM, 'rel' => self::NOTHING, 'deleted' => true], ['id' => $id]);
if (!DBA::exists('contact', ['uri-id' => $contact['uri-id'], 'deleted' => false])) {
Avatar::deleteCache($contact);
}
// Delete it in the background
Worker::add(PRIORITY_MEDIUM, 'Contact\Remove', $id);
Worker::add(Worker::PRIORITY_MEDIUM, 'Contact\Remove', $id);
}
/**
@ -908,7 +943,7 @@ class Contact
if (in_array($contact['rel'], [self::SHARING, self::FRIEND])) {
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']);
if (!empty($cdata['public'])) {
Worker::add(PRIORITY_HIGH, 'Contact\Unfollow', $cdata['public'], $contact['uid']);
Worker::add(Worker::PRIORITY_HIGH, 'Contact\Unfollow', $cdata['public'], $contact['uid']);
}
}
@ -938,7 +973,7 @@ class Contact
if (in_array($contact['rel'], [self::FOLLOWER, self::FRIEND])) {
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']);
if (!empty($cdata['public'])) {
Worker::add(PRIORITY_HIGH, 'Contact\RevokeFollow', $cdata['public'], $contact['uid']);
Worker::add(Worker::PRIORITY_HIGH, 'Contact\RevokeFollow', $cdata['public'], $contact['uid']);
}
}
@ -966,11 +1001,11 @@ class Contact
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']);
if (in_array($contact['rel'], [self::SHARING, self::FRIEND]) && !empty($cdata['public'])) {
Worker::add(PRIORITY_HIGH, 'Contact\Unfollow', $cdata['public'], $contact['uid']);
Worker::add(Worker::PRIORITY_HIGH, 'Contact\Unfollow', $cdata['public'], $contact['uid']);
}
if (in_array($contact['rel'], [self::FOLLOWER, self::FRIEND]) && !empty($cdata['public'])) {
Worker::add(PRIORITY_HIGH, 'Contact\RevokeFollow', $cdata['public'], $contact['uid']);
Worker::add(Worker::PRIORITY_HIGH, 'Contact\RevokeFollow', $cdata['public'], $contact['uid']);
}
self::remove($contact['id']);
@ -1103,7 +1138,7 @@ class Contact
$photos_link = '';
if ($uid == 0) {
$uid = local_user();
$uid = DI::userSession()->getLocalUserId();
}
if (empty($contact['uid']) || ($contact['uid'] != $uid)) {
@ -1124,7 +1159,7 @@ class Contact
$sparkle = false;
if (($contact['network'] === Protocol::DFRN) && !$contact['self'] && empty($contact['pending'])) {
$sparkle = true;
$profile_link = DI::baseUrl() . '/redir/' . $contact['id'];
$profile_link = 'contact/redir/' . $contact['id'];
} else {
$profile_link = $contact['url'];
}
@ -1135,25 +1170,25 @@ class Contact
if ($sparkle) {
$status_link = $profile_link . '/status';
$photos_link = str_replace('/profile/', '/photos/', $profile_link);
$photos_link = $profile_link . '/photos';
$profile_link = $profile_link . '/profile';
}
if (self::canReceivePrivateMessages($contact) && empty($contact['pending'])) {
$pm_url = DI::baseUrl() . '/message/new/' . $contact['id'];
$pm_url = 'message/new/' . $contact['id'];
}
$contact_url = DI::baseUrl() . '/contact/' . $contact['id'];
$contact_url = 'contact/' . $contact['id'];
$posts_link = DI::baseUrl() . '/contact/' . $contact['id'] . '/conversations';
$posts_link = 'contact/' . $contact['id'] . '/conversations';
$follow_link = '';
$unfollow_link = '';
if (!$contact['self'] && Protocol::supportsFollow($contact['network'])) {
if ($contact['uid'] && in_array($contact['rel'], [self::SHARING, self::FRIEND])) {
$unfollow_link = 'unfollow?url=' . urlencode($contact['url']) . '&auto=1';
$unfollow_link = 'contact/unfollow?url=' . urlencode($contact['url']) . '&auto=1';
} elseif(!$contact['pending']) {
$follow_link = 'follow?url=' . urlencode($contact['url']) . '&auto=1';
$follow_link = 'contact/follow?url=' . urlencode($contact['url']) . '&auto=1';
}
}
@ -1167,7 +1202,7 @@ class Contact
'network' => [DI::l10n()->t('Network Posts') , $posts_link , false],
'edit' => [DI::l10n()->t('View Contact') , $contact_url , false],
'follow' => [DI::l10n()->t('Connect/Follow'), $follow_link , true],
'unfollow'=> [DI::l10n()->t('UnFollow') , $unfollow_link, true],
'unfollow'=> [DI::l10n()->t('Unfollow') , $unfollow_link, true],
];
} else {
$menu = [
@ -1178,7 +1213,7 @@ class Contact
'edit' => [DI::l10n()->t('View Contact') , $contact_url , false],
'pm' => [DI::l10n()->t('Send PM') , $pm_url , false],
'follow' => [DI::l10n()->t('Connect/Follow'), $follow_link , true],
'unfollow'=> [DI::l10n()->t('UnFollow') , $unfollow_link , true],
'unfollow'=> [DI::l10n()->t('Unfollow') , $unfollow_link , true],
];
if (!empty($contact['pending'])) {
@ -1248,7 +1283,7 @@ class Contact
$contact_id = $contact['id'];
if (Probe::isProbable($contact['network']) && ($contact['next-update'] < DateTimeFormat::utcNow())) {
Worker::add(['priority' => PRIORITY_LOW, 'dont_fork' => true], 'UpdateContact', $contact['id']);
Worker::add(['priority' => Worker::PRIORITY_LOW, 'dont_fork' => true], 'UpdateContact', $contact['id']);
}
if (empty($update) && (!empty($contact['uri-id']) || is_bool($update))) {
@ -1334,9 +1369,10 @@ class Contact
'writable' => 1,
'blocked' => 0,
'readonly' => 0,
'pending' => 0];
'pending' => 0,
];
$condition = ['nurl' => Strings::normaliseLink($data["url"]), 'uid' => $uid, 'deleted' => false];
$condition = ['nurl' => Strings::normaliseLink($data['url']), 'uid' => $uid, 'deleted' => false];
// Before inserting we do check if the entry does exist now.
$contact = DBA::selectFirst('contact', ['id'], $condition, ['order' => ['id']]);
@ -1359,7 +1395,17 @@ class Contact
}
if ($data['network'] == Protocol::DIASPORA) {
FContact::updateFromProbeArray($data);
try {
DI::dsprContact()->updateFromProbeArray($data);
} catch (\InvalidArgumentException $e) {
Logger::error($e->getMessage(), ['url' => $url, 'data' => $data]);
}
} elseif (!empty($data['networks'][Protocol::DIASPORA])) {
try {
DI::dsprContact()->updateFromProbeArray($data['networks'][Protocol::DIASPORA]);
} catch (\InvalidArgumentException $e) {
Logger::error($e->getMessage(), ['url' => $url, 'data' => $data['networks'][Protocol::DIASPORA]]);
}
}
self::updateFromProbeArray($contact_id, $data);
@ -1506,10 +1552,10 @@ class Contact
if ($thread_mode) {
$condition = ["((`$contact_field` = ? AND `gravity` = ?) OR (`author-id` = ? AND `gravity` = ? AND `vid` = ? AND `thr-parent-id` = `parent-uri-id`)) AND " . $sql,
$cid, GRAVITY_PARENT, $cid, GRAVITY_ACTIVITY, Verb::getID(Activity::ANNOUNCE), local_user()];
$cid, Item::GRAVITY_PARENT, $cid, Item::GRAVITY_ACTIVITY, Verb::getID(Activity::ANNOUNCE), DI::userSession()->getLocalUserId()];
} else {
$condition = ["`$contact_field` = ? AND `gravity` IN (?, ?) AND " . $sql,
$cid, GRAVITY_PARENT, GRAVITY_COMMENT, local_user()];
$cid, Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT, DI::userSession()->getLocalUserId()];
}
if (!empty($parent)) {
@ -1527,10 +1573,10 @@ class Contact
}
if (DI::mode()->isMobile()) {
$itemsPerPage = DI::pConfig()->get(local_user(), 'system', 'itemspage_mobile_network',
$itemsPerPage = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'itemspage_mobile_network',
DI::config()->get('system', 'itemspage_network_mobile'));
} else {
$itemsPerPage = DI::pConfig()->get(local_user(), 'system', 'itemspage_network',
$itemsPerPage = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'itemspage_network',
DI::config()->get('system', 'itemspage_network'));
}
@ -1538,7 +1584,7 @@ class Contact
$params = ['order' => ['received' => true], 'limit' => [$pager->getStart(), $pager->getItemsPerPage()]];
if (DI::pConfig()->get(local_user(), 'system', 'infinite_scroll')) {
if (DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'infinite_scroll')) {
$tpl = Renderer::getMarkupTemplate('infinite_scroll_head.tpl');
$o = Renderer::replaceMacros($tpl, ['$reload_uri' => DI::args()->getQueryString()]);
} else {
@ -1547,27 +1593,27 @@ class Contact
if ($thread_mode) {
$fields = ['uri-id', 'thr-parent-id', 'gravity', 'author-id', 'commented'];
$items = Post::toArray(Post::selectForUser(local_user(), $fields, $condition, $params));
$items = Post::toArray(Post::selectForUser(DI::userSession()->getLocalUserId(), $fields, $condition, $params));
if ($pager->getStart() == 0) {
$cdata = self::getPublicAndUserContactID($cid, local_user());
$cdata = self::getPublicAndUserContactID($cid, DI::userSession()->getLocalUserId());
if (!empty($cdata['public'])) {
$pinned = Post\Collection::selectToArrayForContact($cdata['public'], Post\Collection::FEATURED, $fields);
$items = array_merge($items, $pinned);
}
}
$o .= DI::conversation()->create($items, 'contacts', $update, false, 'pinned_commented', local_user());
$o .= DI::conversation()->create($items, 'contacts', $update, false, 'pinned_commented', DI::userSession()->getLocalUserId());
} else {
$fields = array_merge(Item::DISPLAY_FIELDLIST, ['featured']);
$items = Post::toArray(Post::selectForUser(local_user(), $fields, $condition, $params));
$items = Post::toArray(Post::selectForUser(DI::userSession()->getLocalUserId(), $fields, $condition, $params));
if ($pager->getStart() == 0) {
$cdata = self::getPublicAndUserContactID($cid, local_user());
$cdata = self::getPublicAndUserContactID($cid, DI::userSession()->getLocalUserId());
if (!empty($cdata['public'])) {
$condition = ["`uri-id` IN (SELECT `uri-id` FROM `collection-view` WHERE `cid` = ? AND `type` = ?)",
$cdata['public'], Post\Collection::FEATURED];
$pinned = Post::toArray(Post::selectForUser(local_user(), $fields, $condition, $params));
$pinned = Post::toArray(Post::selectForUser(DI::userSession()->getLocalUserId(), $fields, $condition, $params));
$items = array_merge($pinned, $items);
}
}
@ -1576,7 +1622,7 @@ class Contact
}
if (!$update) {
if (DI::pConfig()->get(local_user(), 'system', 'infinite_scroll')) {
if (DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'infinite_scroll')) {
$o .= HTML::scrollLoader();
} else {
$o .= $pager->renderMinimal(count($items));
@ -1680,11 +1726,9 @@ class Contact
/**
* Return the photo path for a given contact array in the given size
*
* @param array $contact contact array
* @param string $field Fieldname of the photo in the contact array
* @param array $contact contact array
* @param string $size Size of the avatar picture
* @param string $avatar Avatar path that is displayed when no photo had been found
* @param bool $no_update Don't perfom an update if no cached avatar was found
* @param bool $no_update Don't perfom an update if no cached avatar was found
* @return string photo path
*/
private static function getAvatarPath(array $contact, string $size, bool $no_update = false): string
@ -1711,7 +1755,7 @@ class Contact
}
}
return self::getAvatarUrlForId($contact['id'], $size, $contact['updated'] ?? '');
return self::getAvatarUrlForId($contact['id'] ?? 0, $size, $contact['updated'] ?? '');
}
/**
@ -2023,9 +2067,10 @@ class Contact
* @param integer $cid contact id
* @param string $size One of the Proxy::SIZE_* constants
* @param string $updated Contact update date
* @param bool $static If "true" a parameter is added to convert the avatar to a static one
* @return string avatar link
*/
public static function getAvatarUrlForId(int $cid, string $size = '', string $updated = '', string $guid = ''): string
public static function getAvatarUrlForId(int $cid, string $size = '', string $updated = '', string $guid = '', bool $static = false): string
{
// We have to fetch the "updated" variable when it wasn't provided
// The parameter can be provided to improve performance
@ -2055,7 +2100,15 @@ class Contact
$url .= Proxy::PIXEL_LARGE . '/';
break;
}
return $url . ($guid ?: $cid) . ($updated ? '?ts=' . strtotime($updated) : '');
$query_params = [];
if ($updated) {
$query_params['ts'] = strtotime($updated);
}
if ($static) {
$query_params['static'] = true;
}
return $url . ($guid ?: $cid) . (!empty($query_params) ? '?' . http_build_query($query_params) : '');
}
/**
@ -2080,9 +2133,10 @@ class Contact
* @param integer $cid contact id
* @param string $size One of the Proxy::SIZE_* constants
* @param string $updated Contact update date
* @param bool $static If "true" a parameter is added to convert the header to a static one
* @return string header link
*/
public static function getHeaderUrlForId(int $cid, string $size = '', string $updated = '', string $guid = ''): string
public static function getHeaderUrlForId(int $cid, string $size = '', string $updated = '', string $guid = '', bool $static = false): string
{
// We have to fetch the "updated" variable when it wasn't provided
// The parameter can be provided to improve performance
@ -2113,7 +2167,15 @@ class Contact
break;
}
return $url . ($guid ?: $cid) . ($updated ? '?ts=' . strtotime($updated) : '');
$query_params = [];
if ($updated) {
$query_params['ts'] = strtotime($updated);
}
if ($static) {
$query_params['static'] = true;
}
return $url . ($guid ?: $cid) . (!empty($query_params) ? '?' . http_build_query($query_params) : '');
}
/**
@ -2367,7 +2429,7 @@ class Contact
return;
}
Logger::warning('account-user exists for a different contact id', ['account_user' => $account_user, 'id' => $id, 'uid' => $uid, 'uri-id' => $uri_id, 'url' => $url]);
Worker::add(PRIORITY_HIGH, 'MergeContact', $account_user['id'], $id, $uid);
Worker::add(Worker::PRIORITY_HIGH, 'MergeContact', $account_user['id'], $id, $uid);
} elseif (DBA::insert('account-user', ['id' => $id, 'uri-id' => $uri_id, 'uid' => $uid], Database::INSERT_IGNORE)) {
Logger::notice('account-user was added', ['id' => $id, 'uid' => $uid, 'uri-id' => $uri_id, 'url' => $url]);
} else {
@ -2408,7 +2470,7 @@ class Contact
continue;
}
Worker::add(PRIORITY_HIGH, 'MergeContact', $first, $duplicate['id'], $uid);
Worker::add(Worker::PRIORITY_HIGH, 'MergeContact', $first, $duplicate['id'], $uid);
}
DBA::close($duplicates);
Logger::info('Duplicates handled', ['uid' => $uid, 'nurl' => $nurl, 'callstack' => System::callstack(20)]);
@ -2431,13 +2493,23 @@ class Contact
return false;
}
$ret = Probe::uri($contact['url'], $network, $contact['uid']);
$data = Probe::uri($contact['url'], $network, $contact['uid']);
if ($ret['network'] == Protocol::DIASPORA) {
FContact::updateFromProbeArray($ret);
if ($data['network'] == Protocol::DIASPORA) {
try {
DI::dsprContact()->updateFromProbeArray($data);
} catch (\InvalidArgumentException $e) {
Logger::error($e->getMessage(), ['id' => $id, 'network' => $network, 'contact' => $contact, 'data' => $data]);
}
} elseif (!empty($data['networks'][Protocol::DIASPORA])) {
try {
DI::dsprContact()->updateFromProbeArray($data['networks'][Protocol::DIASPORA]);
} catch (\InvalidArgumentException $e) {
Logger::error($e->getMessage(), ['id' => $id, 'network' => $network, 'contact' => $contact, 'data' => $data]);
}
}
return self::updateFromProbeArray($id, $ret);
return self::updateFromProbeArray($id, $data);
}
/**
@ -2610,7 +2682,7 @@ class Contact
if ($ret['network'] == Protocol::ACTIVITYPUB) {
$apcontact = APContact::getByURL($ret['url'], false);
if (!empty($apcontact['featured'])) {
Worker::add(PRIORITY_LOW, 'FetchFeaturedPosts', $ret['url']);
Worker::add(Worker::PRIORITY_LOW, 'FetchFeaturedPosts', $ret['url']);
}
}
@ -2619,7 +2691,7 @@ class Contact
}
$update = false;
$guid = ($ret['guid'] ?? '') ?: Item::guidFromUri($ret['url'], parse_url($ret['url'], PHP_URL_HOST));
$guid = ($ret['guid'] ?? '') ?: Item::guidFromUri($ret['url']);
// make sure to not overwrite existing values with blank entries except some technical fields
$keep = ['batch', 'notify', 'poll', 'request', 'confirm', 'poco', 'baseurl'];
@ -2651,7 +2723,7 @@ class Contact
self::updateContact($id, $uid, $uriid, $contact['url'], ['failed' => false, 'local-data' => $has_local_data, 'last-update' => $updated, 'next-update' => $success_next_update, 'success_update' => $updated]);
if (Contact\Relation::isDiscoverable($ret['url'])) {
Worker::add(PRIORITY_LOW, 'ContactDiscovery', $ret['url']);
Worker::add(Worker::PRIORITY_LOW, 'ContactDiscovery', $ret['url']);
}
// Update the public contact
@ -2695,7 +2767,7 @@ class Contact
self::updateContact($id, $uid, $ret['uri-id'], $ret['url'], $ret);
if (Contact\Relation::isDiscoverable($ret['url'])) {
Worker::add(PRIORITY_LOW, 'ContactDiscovery', $ret['url']);
Worker::add(Worker::PRIORITY_LOW, 'ContactDiscovery', $ret['url']);
}
return true;
@ -2855,30 +2927,30 @@ class Contact
// do we have enough information?
if (empty($protocol) || ($protocol == Protocol::PHANTOM) || (empty($ret['url']) && empty($ret['addr']))) {
$result['message'] .= DI::l10n()->t('The profile address specified does not provide adequate information.') . EOL;
$result['message'] .= DI::l10n()->t('The profile address specified does not provide adequate information.') . '<br />';
if (empty($ret['poll'])) {
$result['message'] .= DI::l10n()->t('No compatible communication protocols or feeds were discovered.') . EOL;
$result['message'] .= DI::l10n()->t('No compatible communication protocols or feeds were discovered.') . '<br />';
}
if (empty($ret['name'])) {
$result['message'] .= DI::l10n()->t('An author or name was not found.') . EOL;
$result['message'] .= DI::l10n()->t('An author or name was not found.') . '<br />';
}
if (empty($ret['url'])) {
$result['message'] .= DI::l10n()->t('No browser URL could be matched to this address.') . EOL;
$result['message'] .= DI::l10n()->t('No browser URL could be matched to this address.') . '<br />';
}
if (strpos($ret['url'], '@') !== false) {
$result['message'] .= DI::l10n()->t('Unable to match @-style Identity Address with a known protocol or email contact.') . EOL;
$result['message'] .= DI::l10n()->t('Use mailto: in front of address to force email check.') . EOL;
$result['message'] .= DI::l10n()->t('Unable to match @-style Identity Address with a known protocol or email contact.') . '<br />';
$result['message'] .= DI::l10n()->t('Use mailto: in front of address to force email check.') . '<br />';
}
return $result;
}
if ($protocol === Protocol::OSTATUS && DI::config()->get('system', 'ostatus_disabled')) {
$result['message'] .= DI::l10n()->t('The profile address specified belongs to a network which has been disabled on this site.') . EOL;
$result['message'] .= DI::l10n()->t('The profile address specified belongs to a network which has been disabled on this site.') . '<br />';
$ret['notify'] = '';
}
if (!$ret['notify']) {
$result['message'] .= DI::l10n()->t('Limited profile. This person will be unable to receive direct/personal notifications from you.') . EOL;
$result['message'] .= DI::l10n()->t('Limited profile. This person will be unable to receive direct/personal notifications from you.') . '<br />';
}
$writeable = ((($protocol === Protocol::OSTATUS) && ($ret['notify'])) ? 1 : 0);
@ -2937,7 +3009,7 @@ class Contact
$contact = DBA::selectFirst('contact', [], ['url' => $ret['url'], 'network' => $ret['network'], 'uid' => $uid]);
if (!DBA::isResult($contact)) {
$result['message'] .= DI::l10n()->t('Unable to retrieve contact information.') . EOL;
$result['message'] .= DI::l10n()->t('Unable to retrieve contact information.') . '<br />';
return $result;
}
@ -2951,13 +3023,13 @@ 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');
Worker::add(Worker::PRIORITY_HIGH, 'OnePoll', $contact_id, 'force');
}
if ($probed) {
self::updateFromProbeArray($contact_id, $ret);
} else {
Worker::add(PRIORITY_HIGH, 'UpdateContact', $contact_id);
Worker::add(Worker::PRIORITY_HIGH, 'UpdateContact', $contact_id);
}
$result['success'] = Protocol::follow($uid, $contact, $protocol);
@ -3132,11 +3204,14 @@ class Contact
return;
}
Worker::add(Worker::PRIORITY_LOW, 'ContactDiscoveryForUser', $contact['uid']);
self::clearFollowerFollowingEndpointCache($contact['uid']);
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']);
DI::notification()->deleteForUserByVerb($contact['uid'], Activity::FOLLOW, ['actor-id' => $cdata['public']]);
if (!empty($cdata['public'])) {
DI::notification()->deleteForUserByVerb($contact['uid'], Activity::FOLLOW, ['actor-id' => $cdata['public']]);
}
}
/**
@ -3155,6 +3230,8 @@ class Contact
} else {
self::update(['rel' => self::FOLLOWER], ['id' => $contact['id']]);
}
Worker::add(Worker::PRIORITY_LOW, 'ContactDiscoveryForUser', $contact['uid']);
}
/**
@ -3233,7 +3310,7 @@ class Contact
*/
public static function magicLink(string $contact_url, string $url = ''): string
{
if (!Session::isAuthenticated()) {
if (!DI::userSession()->isAuthenticated()) {
return $url ?: $contact_url; // Equivalent to: ($url != '') ? $url : $contact_url;
}
@ -3279,7 +3356,7 @@ class Contact
{
$destination = $url ?: $contact['url']; // Equivalent to ($url != '') ? $url : $contact['url'];
if (!Session::isAuthenticated()) {
if (!DI::userSession()->isAuthenticated()) {
return $destination;
}
@ -3288,7 +3365,7 @@ class Contact
return $url;
}
if (DI::pConfig()->get(local_user(), 'system', 'stay_local') && ($url == '')) {
if (DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'stay_local') && ($url == '')) {
return 'contact/' . $contact['id'] . '/conversations';
}
@ -3300,7 +3377,7 @@ class Contact
return $destination;
}
$redirect = 'redir/' . $contact['id'];
$redirect = 'contact/redir/' . $contact['id'];
if (($url != '') && !Strings::compareLink($contact['url'], $url)) {
$redirect .= '?url=' . $url;
@ -3349,11 +3426,13 @@ class Contact
* @param string $search Name or nick
* @param string $mode Search mode (e.g. "community")
* @param int $uid User ID
* @param int $limit Maximum amount of returned values
* @param int $offset Limit offset
*
* @return array with search results
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function searchByName(string $search, string $mode = '', int $uid = 0): array
public static function searchByName(string $search, string $mode = '', int $uid = 0, int $limit = 0, int $offset = 0): array
{
if (empty($search)) {
return [];
@ -3373,6 +3452,8 @@ class Contact
if ($uid == 0) {
$condition['blocked'] = false;
} else {
$condition['rel'] = [Contact::SHARING, Contact::FRIEND];
}
// check if we search only communities or every contact
@ -3382,12 +3463,19 @@ class Contact
$search .= '%';
$params = [];
if (!empty($limit) && !empty($offset)) {
$params['limit'] = [$offset, $limit];
} elseif (!empty($limit)) {
$params['limit'] = $limit;
}
$condition = DBA::mergeConditions($condition,
["(NOT `unsearchable` OR `nurl` IN (SELECT `nurl` FROM `owner-view` WHERE `publish` OR `net-publish`))
AND (`addr` LIKE ? OR `name` LIKE ? OR `nick` LIKE ?)", $search, $search, $search]);
$contacts = self::selectToArray([], $condition);
return $contacts;
return self::selectToArray([], $condition, $params);
}
/**
@ -3409,10 +3497,10 @@ class Contact
}
$contact = self::getByURL($url, false, ['id', 'network', 'next-update']);
if (empty($contact['id']) && Network::isValidHttpUrl($url)) {
Worker::add(PRIORITY_LOW, 'AddContact', 0, $url);
Worker::add(Worker::PRIORITY_LOW, 'AddContact', 0, $url);
++$added;
} elseif (!empty($contact['network']) && Probe::isProbable($contact['network']) && ($contact['next-update'] < DateTimeFormat::utcNow())) {
Worker::add(['priority' => PRIORITY_LOW, 'dont_fork' => true], 'UpdateContact', $contact['id']);
Worker::add(['priority' => Worker::PRIORITY_LOW, 'dont_fork' => true], 'UpdateContact', $contact['id']);
++$updated;
} else {
++$unchanged;
@ -3442,4 +3530,17 @@ class Contact
return [];
}
/**
* Checks, if contacts with the given condition exists
*
* @param array $condition
*
* @return bool
* @throws \Exception
*/
public static function exists(array $condition): bool
{
return DBA::exists('contact', $condition);
}
}

View file

@ -22,6 +22,7 @@
namespace Friendica\Model\Contact;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
/**
@ -53,7 +54,7 @@ class Group
AND NOT `contact`.`pending`
ORDER BY `contact`.`name` ASC',
$gid,
local_user()
DI::userSession()->getLocalUserId()
);
if (DBA::isResult($stmt)) {
@ -78,7 +79,7 @@ class Group
{
return Contact::selectToArray([], ["`uid` = ? AND NOT `self` AND NOT `deleted` AND NOT `blocked` AND NOT `pending` AND NOT `failed`
AND `id` NOT IN (SELECT DISTINCT(`contact-id`) FROM `group_member` INNER JOIN `group` ON `group`.`id` = `group_member`.`gid`
WHERE `group`.`uid` = ?)", $uid, $uid]);
WHERE `group`.`uid` = ? AND `contact-id` = `contact`.`id`)", $uid, $uid]);
}
/**

View file

@ -68,6 +68,26 @@ class Relation
DBA::insert('contact-relation', ['last-interaction' => $interaction_date, 'cid' => $target, 'relation-cid' => $actor], Database::INSERT_UPDATE);
}
/**
* Fetch the followers of a given user
*
* @param integer $uid User ID
* @return void
*/
public static function discoverByUser(int $uid)
{
$contact = Contact::selectFirst(['id', 'url', 'network'], ['uid' => $uid, 'self' => true]);
if (empty($contact)) {
Logger::warning('Self contact for user not found', ['uid' => $uid]);
return;
}
$followers = self::getContacts($uid, [Contact::FOLLOWER, Contact::FRIEND]);
$followings = self::getContacts($uid, [Contact::SHARING, Contact::FRIEND]);
self::updateFollowersFollowings($contact, $followers, $followings);
}
/**
* Fetches the followers of a given profile and adds them
*
@ -113,13 +133,27 @@ class Relation
$followings = [];
}
self::updateFollowersFollowings($contact, $followers, $followings);
}
/**
* Update followers and followings for the given contact
*
* @param array $contact
* @param array $followers
* @param array $followings
* @return void
*/
private static function updateFollowersFollowings(array $contact, array $followers, array $followings)
{
if (empty($followers) && empty($followings)) {
Contact::update(['last-discovery' => DateTimeFormat::utcNow()], ['id' => $contact['id']]);
Logger::info('The contact does not offer discoverable data', ['id' => $contact['id'], 'url' => $url, 'network' => $contact['network']]);
Logger::info('The contact does not offer discoverable data', ['id' => $contact['id'], 'url' => $contact['url'], 'network' => $contact['network']]);
return;
}
$target = $contact['id'];
$url = $contact['url'];
if (!empty($followers)) {
// Clear the follower list, since it will be recreated in the next step
@ -260,6 +294,59 @@ class Relation
return true;
}
/**
* Check if the cached suggestion is outdated
*
* @param integer $uid
* @return boolean
*/
static public function areSuggestionsOutdated(int $uid): bool
{
return DI::pConfig()->get($uid, 'suggestion', 'last_update') + 3600 < time();
}
/**
* Update contact suggestions for a given user
*
* @param integer $uid
* @return void
*/
static public function updateCachedSuggestions(int $uid)
{
if (!self::areSuggestionsOutdated($uid)) {
return;
}
DBA::delete('account-suggestion', ['uid' => $uid, 'ignore' => false]);
foreach (self::getSuggestions($uid) as $contact) {
DBA::insert('account-suggestion', ['uri-id' => $contact['uri-id'], 'uid' => $uid, 'level' => 1], Database::INSERT_IGNORE);
}
DI::pConfig()->set($uid, 'suggestion', 'last_update', time());
}
/**
* Returns a cached array of suggested contacts for given user id
*
* @param int $uid User id
* @param int $start optional, default 0
* @param int $limit optional, default 80
* @return array
*/
static public function getCachedSuggestions(int $uid, int $start = 0, int $limit = 80): array
{
$condition = ["`uid` = ? AND `uri-id` IN (SELECT `uri-id` FROM `account-suggestion` WHERE NOT `ignore` AND `uid` = ?)", 0, $uid];
$params = ['limit' => [$start, $limit]];
$cached = DBA::selectToArray('contact', [], $condition, $params);
if (!empty($cached)) {
return $cached;
} else {
return self::getSuggestions($uid, $start, $limit);
}
}
/**
* Returns an array of suggested contacts for given user id
*
@ -270,6 +357,10 @@ class Relation
*/
static public function getSuggestions(int $uid, int $start = 0, int $limit = 80): array
{
if ($uid == 0) {
return [];
}
$cid = Contact::getPublicIdByUserId($uid);
$totallimit = $start + $limit;
$contacts = [];
@ -284,12 +375,13 @@ class Relation
$results = DBA::select('contact', [], ["`id` IN (SELECT `cid` FROM `contact-relation` WHERE `relation-cid` IN
(SELECT `cid` FROM `contact-relation` WHERE `relation-cid` = ?)
AND NOT `cid` IN (SELECT `id` FROM `contact` WHERE `uid` = ? AND `nurl` IN
(SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?))))
AND NOT `hidden` AND `network` IN (?, ?, ?, ?)",
(SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?))) AND `id` = `cid`)
AND NOT `hidden` AND `network` IN (?, ?, ?, ?)
AND NOT `uri-id` IN (SELECT `uri-id` FROM `account-suggestion` WHERE `uri-id` = `contact`.`uri-id` AND `uid` = ?)",
$cid,
0,
$uid, Contact::FRIEND, Contact::SHARING,
Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus,
Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus, $uid
], [
'order' => ['last-item' => true],
'limit' => $totallimit,
@ -314,10 +406,11 @@ class Relation
["`id` IN (SELECT `cid` FROM `contact-relation` WHERE `relation-cid` IN
(SELECT `relation-cid` FROM `contact-relation` WHERE `cid` = ?)
AND NOT `cid` IN (SELECT `id` FROM `contact` WHERE `uid` = ? AND `nurl` IN
(SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?))))
AND NOT `hidden` AND `network` IN (?, ?, ?, ?)",
(SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?))) AND `id` = `cid`)
AND NOT `hidden` AND `network` IN (?, ?, ?, ?)
AND NOT `uri-id` IN (SELECT `uri-id` FROM `account-suggestion` WHERE `uri-id` = `contact`.`uri-id` AND `uid` = ?)",
$cid, 0, $uid, Contact::FRIEND, Contact::SHARING,
Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus],
Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus, $uid],
['order' => ['last-item' => true], 'limit' => $totallimit]
);
@ -335,9 +428,10 @@ class Relation
// The query returns contacts that follow the given user but aren't followed by that user.
$results = DBA::select('contact', [],
["`nurl` IN (SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` = ?)
AND NOT `hidden` AND `uid` = ? AND `network` IN (?, ?, ?, ?)",
AND NOT `hidden` AND `uid` = ? AND `network` IN (?, ?, ?, ?)
AND NOT `uri-id` IN (SELECT `uri-id` FROM `account-suggestion` WHERE `uri-id` = `contact`.`uri-id` AND `uid` = ?)",
$uid, Contact::FOLLOWER, 0,
Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus],
Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus, $uid],
['order' => ['last-item' => true], 'limit' => $totallimit]
);
@ -354,10 +448,11 @@ class Relation
// The query returns any contact that isn't followed by that user.
$results = DBA::select('contact', [],
["NOT `nurl` IN (SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?))
AND NOT `hidden` AND `uid` = ? AND `network` IN (?, ?, ?, ?)",
["NOT `nurl` IN (SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?) AND `nurl` = `nurl`)
AND NOT `hidden` AND `uid` = ? AND `network` IN (?, ?, ?, ?)
AND NOT `uri-id` IN (SELECT `uri-id` FROM `account-suggestion` WHERE `uri-id` = `contact`.`uri-id` AND `uid` = ?)",
$uid, Contact::FRIEND, Contact::SHARING, 0,
Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus],
Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus, $uid],
['order' => ['last-item' => true], 'limit' => $totallimit]
);

View file

@ -21,18 +21,20 @@
namespace Friendica\Model;
use Friendica\Content\Feature;
use Friendica\Content\Text\BBCode;
use Friendica\Core\Hook;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\Renderer;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Network\HTTPException;
use Friendica\Protocol\Activity;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Map;
use Friendica\Util\Strings;
use Friendica\Util\Temporal;
use Friendica\Util\XML;
/**
@ -279,7 +281,7 @@ class Event
if (!DBA::isResult($existing_event)) {
return 0;
}
if ($existing_event['edited'] === $event['edited']) {
return $event['id'];
}
@ -410,7 +412,7 @@ class Event
public static function getStrings(): array
{
// First day of the week (0 = Sunday).
$firstDay = DI::pConfig()->get(local_user(), 'system', 'first_day_of_week', 0);
$firstDay = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'first_day_of_week', 0);
$i18n = [
"firstDay" => $firstDay,
@ -495,157 +497,197 @@ class Event
}
/**
* Get an event by its event ID.
* Returns the owner array of a given nickname
* Additionally, it can check if the owner array is selectable
*
* @param int $owner_uid The User ID of the owner of the event
* @param int $event_id The ID of the event in the event table
* @param string $sql_extra
* @param string $nickname
*
* @return array the owner array
* @throws HTTPException\InternalServerErrorException
* @throws HTTPException\NotFoundException The given nickname does not exist
* @throws HTTPException\UnauthorizedException The access for the given nickname is restricted
*/
public static function getOwnerForNickname(string $nickname): array
{
$owner = User::getOwnerDataByNick($nickname);
if (empty($owner) || $owner['account_removed'] || $owner['account_expired']) {
throw new HTTPException\NotFoundException(DI::l10n()->t('User not found.'));
}
if (!DI::userSession()->isAuthenticated() && $owner['hidewall']) {
throw new HTTPException\UnauthorizedException(DI::l10n()->t('Access to this profile has been restricted.'));
}
if (!DI::userSession()->isAuthenticated() && !Feature::isEnabled($owner['uid'], 'public_calendar')) {
throw new HTTPException\UnauthorizedException(DI::l10n()->t('Permission denied.'));
}
return $owner;
}
/**
* Get an event by its event ID. Checks permissions.
*
* @param int $owner_uid The User ID of the owner of the event
* @param int $event_id The ID of the event in the event table
* @param string|null $nickname a possible nickname to search for instead of the owner uid
* @return array Query result
* @throws \Exception
*/
public static function getListById(int $owner_uid, int $event_id, string $sql_extra = ''): array
public static function getByIdAndUid(int $owner_uid, int $event_id): array
{
$return = [];
// Ownly allow events if there is a valid owner_id.
// Only allow events if there is a valid owner_id.
if ($owner_uid == 0) {
return $return;
return [];
}
// get the permissions
$sql_perms = Item::getPermissionsSQLByUserId($owner_uid);
// Query for the event by event id
$events = DBA::toArray(DBA::p("SELECT `event`.*, `post-user`.`id` AS `itemid` FROM `event`
LEFT JOIN `post-user` ON `post-user`.`event-id` = `event`.`id` AND `post-user`.`uid` = `event`.`uid`
WHERE `event`.`uid` = ? AND `event`.`id` = ? $sql_extra",
$owner_uid, $event_id));
if (DBA::isResult($events)) {
$return = self::removeDuplicates($events);
$events = DBA::toArray(DBA::p(
"SELECT `event`.*, `post-user`.`id` AS `itemid` FROM `event`
LEFT JOIN `post-user`
ON `post-user`.`event-id` = `event`.`id`
AND `post-user`.`uid` = `event`.`uid`
WHERE `event`.`id` = ?
AND `event`.`uid` = ?
$sql_perms",
$event_id, $owner_uid
));
if (empty($events)) {
throw new HTTPException\NotFoundException(DI::l10n()->t('Event not found.'));
}
return $return;
return $events[0];
}
/**
* Get all events in a specific time frame.
*
* @param int $owner_uid The User ID of the owner of the events.
* @param array $event_params An associative array with
* int 'ignore' =>
* string 'start' => Start time of the timeframe.
* string 'finish' => Finish time of the timeframe.
*
* @param string $sql_extra Additional sql conditions (e.g. permission request).
* @param int $owner_uid The User ID of the owner of the events.
* @param string|null $start Start time of the timeframe.
* @param string|null $finish Finish time of the timeframe.
* @param bool|null $ignore Filters ignored events (false: unignored events, true: ignored events, null: all events)
*
* @return array Query results.
* @throws \Exception
* @throws HTTPException\NotFoundException
* @throws HTTPException\UnauthorizedException
*/
public static function getListByDate(int $owner_uid, array $event_params, string $sql_extra = ''): array
public static function getListByDate(int $owner_uid, string $start = null, string $finish = null, ?bool $ignore = false): array
{
$return = [];
// Only allow events if there is a valid owner_id.
if ($owner_uid == 0) {
return $return;
return [];
}
// get the permissions
$sql_perms = Item::getPermissionsSQLByUserId($owner_uid);
if (empty($start) || empty($finish)) {
$y = intval(DateTimeFormat::localNow('Y'));
$m = intval(DateTimeFormat::localNow('m'));
if (empty($start)) {
$start = sprintf('%d-%d-%d %d:%d:%d', $y, $m, 1, 0, 0, 0);
} else {
$dim = Temporal::getDaysInMonth($y, $m);
$finish = sprintf('%d-%d-%d %d:%d:%d', $y, $m, $dim, 23, 59, 59);
}
}
if ($ignore === true) {
$sql_ignore = " AND `event`.`ignore` = 1";
} elseif ($ignore === false) {
$sql_ignore = " AND `event`.`ignore` = 0";
} else {
$sql_ignore = "";
}
// Query for the event by date.
$events = DBA::toArray(DBA::p("SELECT `event`.*, `post-user`.`id` AS `itemid` FROM `event`
LEFT JOIN `post-user` ON `post-user`.`event-id` = `event`.`id` AND `post-user`.`uid` = `event`.`uid`
WHERE `event`.`uid` = ? AND `event`.`ignore` = ?
AND (`finish` >= ? OR (`nofinish` AND `start` >= ?)) AND `start` <= ?
" . $sql_extra,
$owner_uid, $event_params['ignore'],
$event_params['start'], $event_params['start'], $event_params['finish']
$events = DBA::toArray(DBA::p(
"SELECT `event`.*, `post-user`.`id` AS `itemid` FROM `event`
LEFT JOIN `post-user`
ON `post-user`.`event-id` = `event`.`id`
AND `post-user`.`uid` = `event`.`uid`
WHERE `event`.`uid` = ?
$sql_ignore
AND (`finish` >= ? OR (`nofinish` AND `start` >= ?))
AND `start` <= ?
$sql_perms",
$owner_uid,
$start, $start,
$finish
));
if (DBA::isResult($events)) {
$return = self::removeDuplicates($events);
}
return $return;
$events = self::removeDuplicates($events);
return self::sortByDate($events);
}
/**
* Convert an array query results in an array which could be used by the events template.
* Convert an event in an array which could be used by the event template.
*
* @param array $event_result Event query array.
* @param array $event Event query array.
* @return array Event array for the template.
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function prepareListForTemplate(array $event_result): array
public static function prepareForItem(array $event): array
{
$event_list = [];
$last_date = '';
$fmt = DI::l10n()->t('l, F j');
foreach ($event_result as $event) {
$item = Post::selectFirst(['plink', 'author-name', 'author-avatar', 'author-link', 'private', 'uri-id'], ['id' => $event['itemid']]);
if (!DBA::isResult($item)) {
// Using default values when no item had been found
$item = ['plink' => '', 'author-name' => '', 'author-avatar' => '', 'author-link' => '', 'private' => Item::PUBLIC, 'uri-id' => ($event['uri-id'] ?? 0)];
}
$event = array_merge($event, $item);
$start = DateTimeFormat::local($event['start'], 'c');
$j = DateTimeFormat::local($event['start'], 'j');
$day = DateTimeFormat::local($event['start'], $fmt);
$day = DI::l10n()->getDay($day);
if ($event['nofinish']) {
$end = null;
} else {
$end = DateTimeFormat::local($event['finish'], 'c');
}
$is_first = ($day !== $last_date);
$last_date = $day;
// Show edit and drop actions only if the user is the owner of the event and the event
// is a real event (no bithdays).
$edit = null;
$copy = null;
$drop = null;
if (local_user() && local_user() == $event['uid'] && $event['type'] == 'event') {
$edit = !$event['cid'] ? [DI::baseUrl() . '/events/event/' . $event['id'], DI::l10n()->t('Edit event') , '', ''] : null;
$copy = !$event['cid'] ? [DI::baseUrl() . '/events/copy/' . $event['id'] , DI::l10n()->t('Duplicate event'), '', ''] : null;
$drop = [DI::baseUrl() . '/events/drop/' . $event['id'] , DI::l10n()->t('Delete event') , '', ''];
}
$title = BBCode::convertForUriId($event['uri-id'], Strings::escapeHtml($event['summary']));
if (!$title) {
list($title, $_trash) = explode("<br", BBCode::convertForUriId($event['uri-id'], Strings::escapeHtml($event['desc'])), BBCode::API);
}
$author_link = $event['author-link'];
$event['author-link'] = Contact::magicLink($author_link);
$html = self::getHTML($event);
$event['summary'] = BBCode::convertForUriId($event['uri-id'], Strings::escapeHtml($event['summary']));
$event['desc'] = BBCode::convertForUriId($event['uri-id'], Strings::escapeHtml($event['desc']));
$event['location'] = BBCode::convertForUriId($event['uri-id'], Strings::escapeHtml($event['location']));
$event_list[] = [
'id' => $event['id'],
'start' => $start,
'end' => $end,
'allDay' => false,
'title' => $title,
'j' => $j,
'd' => $day,
'edit' => $edit,
'drop' => $drop,
'copy' => $copy,
'is_first' => $is_first,
'item' => $event,
'html' => $html,
'plink' => Item::getPlink($event),
];
$item = Post::selectFirst(['plink', 'author-name', 'author-network', 'author-id', 'author-avatar', 'author-link', 'private', 'uri-id'], ['id' => $event['itemid']]);
if (empty($item)) {
// Using default values when no item had been found
$item = ['plink' => '', 'author-name' => '', 'author-avatar' => '', 'author-link' => '', 'private' => Item::PUBLIC, 'uri-id' => ($event['uri-id'] ?? 0)];
}
return $event_list;
$event = array_merge($event, $item);
$start = DateTimeFormat::local($event['start'], 'c');
$j = DateTimeFormat::local($event['start'], 'j');
$day = DateTimeFormat::local($event['start'], $fmt);
$day = DI::l10n()->getDay($day);
if ($event['nofinish']) {
$end = null;
} else {
$end = DateTimeFormat::local($event['finish'], 'c');
}
// Show edit and drop actions only if the user is the owner of the event and the event
// is a real event (no bithdays).
$edit = null;
$copy = null;
$drop = null;
if (DI::userSession()->getLocalUserId() && DI::userSession()->getLocalUserId() == $event['uid'] && $event['type'] == 'event') {
$edit = !$event['cid'] ? ['calendar/event/edit/' . $event['id'], DI::l10n()->t('Edit event') , '', ''] : null;
$copy = !$event['cid'] ? ['calendar/event/copy/' . $event['id'] , DI::l10n()->t('Duplicate event'), '', ''] : null;
$drop = ['calendar/api/delete/' . $event['id'] , DI::l10n()->t('Delete event') , '', ''];
}
$title = BBCode::convertForUriId($event['uri-id'], Strings::escapeHtml($event['summary']));
if (!$title) {
[$title, $_trash] = explode("<br", BBCode::convertForUriId($event['uri-id'], Strings::escapeHtml($event['desc'])), BBCode::TWITTER_API);
}
$event['author-link'] = Contact::magicLink($event['author-link']);
return [
'id' => $event['id'],
'start' => $start,
'end' => $end,
'allDay' => false,
'title' => $title,
'j' => $j,
'd' => $day,
'edit' => $edit,
'drop' => $drop,
'copy' => $copy,
'item' => $event,
'html' => self::getHTML($event),
'plink' => Item::getPlink($event),
];
}
/**
@ -653,8 +695,6 @@ class Event
*
* @param array $events Query result for events.
* @param string $format The output format (ical/csv).
*
* @param string $timezone Timezone (missing parameter!)
* @return string Content according to selected export format.
*
* @todo Implement timezone support
@ -670,14 +710,13 @@ class Event
switch ($format) {
// Format the exported data as a CSV file.
case "csv":
header("Content-type: text/csv");
$o .= '"Subject", "Start Date", "Start Time", "Description", "End Date", "End Time", "Location"' . PHP_EOL;
foreach ($events as $event) {
/// @todo The time / date entries don't include any information about the
/// timezone the event is scheduled in :-/
$tmp1 = strtotime($event['start']);
$tmp2 = strtotime($event['finish']);
$tmp1 = strtotime($event['start']);
$tmp2 = strtotime($event['finish']);
$time_format = "%H:%M:%S";
$date_format = "%Y-%m-%d";
@ -691,7 +730,6 @@ class Event
// Format the exported data as a ics file.
case "ical":
header("Content-type: text/ics");
$o = 'BEGIN:VCALENDAR' . PHP_EOL
. 'VERSION:2.0' . PHP_EOL
. 'PRODID:-//friendica calendar export//0.1//EN' . PHP_EOL;
@ -705,6 +743,8 @@ class Event
// also long lines SHOULD be split at 75 characters length
foreach ($events as $event) {
$o .= 'BEGIN:VEVENT' . PHP_EOL;
$o .= 'UID:' . $event['id'] . PHP_EOL;
$o .= 'DTSTAMP:' . DateTimeFormat::utc($event['created'], 'Ymd\THis\Z') . PHP_EOL;
if ($event['start']) {
$o .= 'DTSTART:' . DateTimeFormat::utc($event['start'], 'Ymd\THis\Z') . PHP_EOL;
@ -718,25 +758,24 @@ class Event
$tmp = $event['summary'];
$tmp = str_replace(PHP_EOL, PHP_EOL . ' ', $tmp);
$tmp = addcslashes($tmp, ',;');
$o .= 'SUMMARY:' . $tmp . PHP_EOL;
$o .= 'SUMMARY:' . $tmp . PHP_EOL;
}
if ($event['desc']) {
$tmp = $event['desc'];
$tmp = str_replace(PHP_EOL, PHP_EOL . ' ', $tmp);
$tmp = addcslashes($tmp, ',;');
$o .= 'DESCRIPTION:' . $tmp . PHP_EOL;
$o .= 'DESCRIPTION:' . $tmp . PHP_EOL;
}
if ($event['location']) {
$tmp = $event['location'];
$tmp = str_replace(PHP_EOL, PHP_EOL . ' ', $tmp);
$tmp = addcslashes($tmp, ',;');
$o .= 'LOCATION:' . $tmp . PHP_EOL;
$o .= 'LOCATION:' . $tmp . PHP_EOL;
}
$o .= 'END:VEVENT' . PHP_EOL;
$o .= PHP_EOL;
}
$o .= 'END:VCALENDAR' . PHP_EOL;
@ -768,14 +807,14 @@ class Event
return $return;
}
$fields = ['start', 'finish', 'summary', 'desc', 'location', 'nofinish'];
$fields = ['id', 'created', 'start', 'finish', 'summary', 'desc', 'location', 'nofinish'];
$conditions = ['uid' => $uid, 'cid' => 0];
// Does the user who requests happen to be the owner of the events
// requested? then show all of your events, otherwise only those that
// don't have limitations set in allow_cid and allow_gid.
if (local_user() != $uid) {
if (DI::userSession()->getLocalUserId() != $uid) {
$conditions += ['allow_cid' => '', 'allow_gid' => ''];
}
@ -859,42 +898,42 @@ class Event
$tformat = DI::l10n()->t('g:i A'); // 8:01 AM.
// Convert the time to different formats.
$dtstart_dt = DI::l10n()->getDay(DateTimeFormat::local($item['event-start'], $dformat));
$dtstart_dt = DI::l10n()->getDay(DateTimeFormat::local($item['event-start'], $dformat));
$dtstart_title = DateTimeFormat::utc($item['event-start'], DateTimeFormat::ATOM);
// Format: Jan till Dec.
$month_short = DI::l10n()->getDayShort(DateTimeFormat::local($item['event-start'], 'M'));
// Format: 1 till 31.
$date_short = DateTimeFormat::local($item['event-start'], 'j');
$start_time = DateTimeFormat::local($item['event-start'], $tformat);
$date_short = DateTimeFormat::local($item['event-start'], 'j');
$start_time = DateTimeFormat::local($item['event-start'], $tformat);
$start_short = DI::l10n()->getDayShort(DateTimeFormat::local($item['event-start'], $dformat_short));
// If the option 'nofinisch' isn't set, we need to format the finish date/time.
if (!$item['event-nofinish']) {
$finish = true;
$dtend_dt = DI::l10n()->getDay(DateTimeFormat::local($item['event-finish'], $dformat));
$finish = true;
$dtend_dt = DI::l10n()->getDay(DateTimeFormat::local($item['event-finish'], $dformat));
$dtend_title = DateTimeFormat::utc($item['event-finish'], DateTimeFormat::ATOM);
$end_short = DI::l10n()->getDayShort(DateTimeFormat::utc($item['event-finish'], $dformat_short));
$end_time = DateTimeFormat::local($item['event-finish'], $tformat);
$end_short = DI::l10n()->getDayShort(DateTimeFormat::utc($item['event-finish'], $dformat_short));
$end_time = DateTimeFormat::local($item['event-finish'], $tformat);
// Check if start and finish time is at the same day.
if (substr($dtstart_title, 0, 10) === substr($dtend_title, 0, 10)) {
$same_date = true;
}
} else {
$dtend_title = '';
$dtend_dt = '';
$end_time = '';
$end_short = '';
$dtend_dt = '';
$end_time = '';
$end_short = '';
}
// Format the event location.
$location = self::locationToArray($item['event-location']);
// Construct the profile link (magic-auth).
$author = ['uid' => 0, 'id' => $item['author-id'],
'network' => $item['author-network'], 'url' => $item['author-link']];
$author = ['uid' => 0, 'id' => $item['author-id'],
'network' => $item['author-network'], 'url' => $item['author-link']];
$profile_link = Contact::magicLinkByContact($author);
$tpl = Renderer::getMarkupTemplate('event_stream_item.tpl');
$tpl = Renderer::getMarkupTemplate('event_stream_item.tpl');
$return = Renderer::replaceMacros($tpl, [
'$id' => $item['event-id'],
'$title' => BBCode::convertForUriId($item['uri-id'], $item['event-summary']),
@ -952,15 +991,15 @@ class Event
if (strpos($s, '[/map]') !== false) {
$found = preg_match("/\[map\](.*?)\[\/map\]/ism", $s, $match);
if (intval($found) > 0 && array_key_exists(1, $match)) {
$location['address'] = $match[1];
$location['address'] = $match[1];
// Remove the map bbcode from the location name.
$location['name'] = str_replace($match[0], "", $s);
}
// Map tag with coordinates - e.g. [map=48.864716,2.349014].
// Map tag with coordinates - e.g. [map=48.864716,2.349014].
} elseif (strpos($s, '[map=') !== false) {
$found = preg_match("/\[map=(.*?)\]/ism", $s, $match);
if (intval($found) > 0 && array_key_exists(1, $match)) {
$location['coordinates'] = $match[1];
$location['coordinates'] = $match[1];
// Remove the map bbcode from the location name.
$location['name'] = str_replace($match[0], "", $s);
}
@ -990,10 +1029,10 @@ class Event
{
// Check for duplicates
$condition = [
'uid' => $contact['uid'],
'cid' => $contact['id'],
'uid' => $contact['uid'],
'cid' => $contact['id'],
'start' => DateTimeFormat::utc($birthday),
'type' => 'birthday'
'type' => 'birthday'
];
if (DBA::exists('event', $condition)) {
return false;
@ -1019,4 +1058,9 @@ class Event
// Check if self::store() was success
return (self::store($values) > 0);
}
public static function setIgnore(int $uid, int $eventId, bool $ignore = true)
{
DBA::update('event', ['ignore' => $ignore], ['id' => $eventId, 'uid' => $uid]);
}
}

View file

@ -1,160 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, 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;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Network\Probe;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Strings;
class FContact
{
/**
* Fetches data for a given handle
*
* @param string $handle The handle
* @param boolean $update true = always update, false = never update, null = update when not found or outdated
*
* @return array the queried data
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function getByURL(string $handle, $update = null): array
{
Logger::debug('Fetch fcontact', ['handle' => $handle, 'update' => $update]);
$person = DBA::selectFirst('fcontact', [], ['network' => Protocol::DIASPORA, 'addr' => $handle]);
if (!DBA::isResult($person)) {
$urls = [$handle, str_replace('http://', 'https://', $handle), Strings::normaliseLink($handle)];
$person = DBA::selectFirst('fcontact', [], ['network' => Protocol::DIASPORA, 'url' => $urls]);
}
if (DBA::isResult($person)) {
Logger::debug('In cache', ['handle' => $handle]);
if (is_null($update)) {
$update = empty($person['guid']) || empty($person['uri-id']) || ($person['created'] <= DBA::NULL_DATETIME);
if (GServer::getNextUpdateDate(true, $person['created'], $person['updated'], false) < DateTimeFormat::utcNow()) {
Logger::debug('Start background update', ['handle' => $handle]);
Worker::add(['priority' => PRIORITY_LOW, 'dont_fork' => true], 'UpdateFContact', $handle);
}
}
} elseif (is_null($update)) {
$update = true;
} else {
$person = [];
}
if ($update) {
Logger::info('create or refresh', ['handle' => $handle]);
$data = Probe::uri($handle, Protocol::DIASPORA);
// Note that Friendica contacts will return a "Diaspora person"
// if Diaspora connectivity is enabled on their server
if ($data['network'] ?? '' === Protocol::DIASPORA) {
self::updateFromProbeArray($data);
$person = self::getByURL($handle, false);
}
}
return $person;
}
/**
* Updates the fcontact table
*
* @param array $arr The fcontact data
* @throws \Exception
*/
public static function updateFromProbeArray(array $arr)
{
$uriid = ItemURI::insert(['uri' => $arr['url'], 'guid' => $arr['guid']]);
$fcontact = DBA::selectFirst('fcontact', ['created'], ['url' => $arr['url'], 'network' => $arr['network']]);
$contact = Contact::getByUriId($uriid, ['id', 'created']);
$apcontact = APContact::getByURL($arr['url'], false);
if (!empty($apcontact)) {
$interacted = $apcontact['following_count'];
$interacting = $apcontact['followers_count'];
$posts = $apcontact['statuses_count'];
} elseif (!empty($contact['id'])) {
$last_interaction = DateTimeFormat::utc('now - 180 days');
$interacted = DBA::count('contact-relation', ["`cid` = ? AND NOT `follows` AND `last-interaction` > ?", $contact['id'], $last_interaction]);
$interacting = DBA::count('contact-relation', ["`relation-cid` = ? AND NOT `follows` AND `last-interaction` > ?", $contact['id'], $last_interaction]);
$posts = DBA::count('post', ['author-id' => $contact['id'], 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT]]);
}
$fields = [
'name' => $arr['name'],
'photo' => $arr['photo'],
'request' => $arr['request'],
'nick' => $arr['nick'],
'addr' => strtolower($arr['addr']),
'guid' => $arr['guid'],
'batch' => $arr['batch'],
'notify' => $arr['notify'],
'poll' => $arr['poll'],
'confirm' => $arr['confirm'],
'alias' => $arr['alias'],
'pubkey' => $arr['pubkey'],
'uri-id' => $uriid,
'interacting_count' => $interacting ?? 0,
'interacted_count' => $interacted ?? 0,
'post_count' => $posts ?? 0,
'updated' => DateTimeFormat::utcNow(),
];
if (empty($fcontact['created'])) {
$fields['created'] = $fields['updated'];
} elseif (!empty($contact['created']) && ($fcontact['created'] <= DBA::NULL_DATETIME)) {
$fields['created'] = $contact['created'];
}
$fields = DI::dbaDefinition()->truncateFieldsForTable('fcontact', $fields);
DBA::update('fcontact', $fields, ['url' => $arr['url'], 'network' => $arr['network']], true);
}
/**
* get a url (scheme://domain.tld/u/user) from a given Diaspora*
* fcontact guid
*
* @param string $fcontact_guid Hexadecimal string guid
* @return string|null the contact url or null
* @throws \Exception
*/
public static function getUrlByGuid(string $fcontact_guid)
{
Logger::info('fcontact', ['guid' => $fcontact_guid]);
$fcontact = DBA::selectFirst('fcontact', ['url'], ["`url` != ? AND `network` = ? AND `guid` = ?", '', Protocol::DIASPORA, $fcontact_guid]);
if (DBA::isResult($fcontact)) {
return $fcontact['url'];
}
return null;
}
}

View file

@ -70,6 +70,7 @@ class GServer
const DETECT_UNSPECIFIC = [self::DETECT_MANUAL, self::DETECT_HEADER, self::DETECT_BODY, self::DETECT_HOST_META, self::DETECT_CONTACTS, self::DETECT_AP_ACTOR];
// Implementation specific endpoints
// @todo Possibly add Lemmy detection via the endpoint /api/v3/site
const DETECT_FRIENDIKA = 10;
const DETECT_FRIENDICA = 11;
const DETECT_STATUSNET = 12;
@ -102,7 +103,7 @@ class GServer
return;
}
Worker::add(PRIORITY_LOW, 'UpdateGServer', $url, $only_nodeinfo);
Worker::add(Worker::PRIORITY_LOW, 'UpdateGServer', $url, $only_nodeinfo);
}
/**
@ -115,12 +116,12 @@ class GServer
*/
public static function getID(string $url, bool $no_check = false): ?int
{
$url = self::cleanURL($url);
if (empty($url)) {
return null;
}
$url = self::cleanURL($url);
$gserver = DBA::selectFirst('gserver', ['id'], ['nurl' => Strings::normaliseLink($url)]);
if (DBA::isResult($gserver)) {
Logger::debug('Got ID for URL', ['id' => $gserver['id'], 'url' => $url, 'callstack' => System::callstack(20)]);
@ -323,6 +324,10 @@ class GServer
$url = str_replace('/index.php', '', $url);
$urlparts = parse_url($url);
if (empty($urlparts)) {
return '';
}
unset($urlparts['user']);
unset($urlparts['pass']);
unset($urlparts['query']);
@ -1209,7 +1214,7 @@ class GServer
if (!empty($data['url'])) {
$serverdata['platform'] = strtolower($data['platform']);
$serverdata['version'] = $data['version'];
$serverdata['version'] = $data['version'] ?? 'N/A';
}
if (!empty($data['plugins'])) {
@ -1325,7 +1330,7 @@ class GServer
private static function validHostMeta(string $url): bool
{
$xrd_timeout = DI::config()->get('system', 'xrd_timeout');
$curlResult = DI::httpClient()->get($url . '/.well-known/host-meta', HttpClientAccept::XRD_XML, [HttpClientOptions::TIMEOUT => $xrd_timeout]);
$curlResult = DI::httpClient()->get($url . Probe::HOST_META, HttpClientAccept::XRD_XML, [HttpClientOptions::TIMEOUT => $xrd_timeout]);
if (!$curlResult->isSuccess()) {
return false;
}
@ -2108,10 +2113,10 @@ class GServer
while ($gserver = DBA::fetch($gservers)) {
Logger::info('Update peer list', ['server' => $gserver['url'], 'id' => $gserver['id']]);
Worker::add(PRIORITY_LOW, 'UpdateServerPeers', $gserver['url']);
Worker::add(Worker::PRIORITY_LOW, 'UpdateServerPeers', $gserver['url']);
Logger::info('Update directory', ['server' => $gserver['url'], 'id' => $gserver['id']]);
Worker::add(PRIORITY_LOW, 'UpdateServerDirectory', $gserver);
Worker::add(Worker::PRIORITY_LOW, 'UpdateServerDirectory', $gserver);
$fields = ['last_poco_query' => DateTimeFormat::utcNow()];
self::update($fields, ['nurl' => $gserver['nurl']]);
@ -2168,7 +2173,7 @@ class GServer
foreach ($servers['instances'] as $server) {
$url = (is_null($server['https_score']) ? 'http' : 'https') . '://' . $server['name'];
self::add($url);
}
}
}
}
}

View file

@ -102,7 +102,7 @@ class Group
$group = DBA::selectFirst('group', ['deleted'], ['id' => $gid]);
if (DBA::isResult($group) && $group['deleted']) {
DBA::update('group', ['deleted' => 0], ['id' => $gid]);
notice(DI::l10n()->t('A deleted group with this name was revived. Existing item permissions <strong>may</strong> apply to this group and any future members. If this is not what you intended, please create another group with a different name.'));
DI::sysmsg()->addNotice(DI::l10n()->t('A deleted group with this name was revived. Existing item permissions <strong>may</strong> apply to this group and any future members. If this is not what you intended, please create another group with a different name.'));
}
return true;
}
@ -187,8 +187,8 @@ class Group
) AS `count`
FROM `group`
WHERE `group`.`uid` = ?;",
local_user(),
local_user()
DI::userSession()->getLocalUserId(),
DI::userSession()->getLocalUserId()
);
return DBA::toArray($stmt);
@ -526,7 +526,7 @@ class Group
*/
public static function sidebarWidget(string $every = 'contact', string $each = 'group', string $editmode = 'standard', $group_id = '', int $cid = 0)
{
if (!local_user()) {
if (!DI::userSession()->getLocalUserId()) {
return '';
}
@ -544,7 +544,7 @@ class Group
$member_of = self::getIdsByContactId($cid);
}
$stmt = DBA::select('group', [], ['deleted' => false, 'uid' => local_user(), 'cid' => null], ['order' => ['name']]);
$stmt = DBA::select('group', [], ['deleted' => false, 'uid' => DI::userSession()->getLocalUserId(), 'cid' => null], ['order' => ['name']]);
while ($group = DBA::fetch($stmt)) {
$selected = (($group_id == $group['id']) ? ' group-selected' : '');

File diff suppressed because it is too large Load diff

View file

@ -160,7 +160,7 @@ class ParsedLogIterator implements \Iterator
* @see Iterator::next()
* @return void
*/
public function next()
public function next(): void
{
$parsed = $this->read();
@ -177,7 +177,7 @@ class ParsedLogIterator implements \Iterator
* @see Iterator::rewind()
* @return void
*/
public function rewind()
public function rewind(): void
{
$this->value = null;
$this->reader->rewind();
@ -200,9 +200,9 @@ class ParsedLogIterator implements \Iterator
* Return current iterator value
*
* @see Iterator::current()
* @return ?ParsedLogLing
* @return ?ParsedLogLine
*/
public function current()
public function current(): ?ParsedLogLine
{
return $this->value;
}

View file

@ -59,8 +59,7 @@ class Mail
}
if (empty($msg['guid'])) {
$host = parse_url($msg['from-url'], PHP_URL_HOST);
$msg['guid'] = Item::guidFromUri($msg['uri'], $host);
$msg['guid'] = Item::guidFromUri($msg['uri'], parse_url($msg['from-url'], PHP_URL_HOST));
}
$msg['created'] = (!empty($msg['created']) ? DateTimeFormat::utc($msg['created']) : DateTimeFormat::utcNow());
@ -137,12 +136,12 @@ class Mail
$subject = DI::l10n()->t('[no subject]');
}
$me = DBA::selectFirst('contact', [], ['uid' => local_user(), 'self' => true]);
$me = DBA::selectFirst('contact', [], ['uid' => DI::userSession()->getLocalUserId(), 'self' => true]);
if (!DBA::isResult($me)) {
return -2;
}
$contacts = ACL::getValidMessageRecipientsForUser(local_user());
$contacts = ACL::getValidMessageRecipientsForUser(DI::userSession()->getLocalUserId());
$contactIndex = array_search($recipient, array_column($contacts, 'id'));
if ($contactIndex === false) {
@ -151,7 +150,7 @@ class Mail
$contact = $contacts[$contactIndex];
Photo::setPermissionFromBody($body, local_user(), $me['id'], '<' . $contact['id'] . '>', '', '', '');
Photo::setPermissionFromBody($body, DI::userSession()->getLocalUserId(), $me['id'], '<' . $contact['id'] . '>', '', '', '');
$guid = System::createUUID();
$uri = Item::newURI($guid);
@ -164,7 +163,7 @@ class Mail
if (strlen($replyto)) {
$reply = true;
$condition = ["`uid` = ? AND (`uri` = ? OR `parent-uri` = ?)",
local_user(), $replyto, $replyto];
DI::userSession()->getLocalUserId(), $replyto, $replyto];
$mail = DBA::selectFirst('mail', ['convid'], $condition);
if (DBA::isResult($mail)) {
$convid = $mail['convid'];
@ -177,7 +176,7 @@ class Mail
$conv_guid = System::createUUID();
$convuri = $contact['addr'] . ':' . $conv_guid;
$fields = ['uid' => local_user(), 'guid' => $conv_guid, 'creator' => $me['addr'],
$fields = ['uid' => DI::userSession()->getLocalUserId(), 'guid' => $conv_guid, 'creator' => $me['addr'],
'created' => DateTimeFormat::utcNow(), 'updated' => DateTimeFormat::utcNow(),
'subject' => $subject, 'recips' => $contact['addr'] . ';' . $me['addr']];
if (DBA::insert('conv', $fields)) {
@ -196,7 +195,7 @@ class Mail
$post_id = self::insert(
[
'uid' => local_user(),
'uid' => DI::userSession()->getLocalUserId(),
'guid' => $guid,
'convid' => $convid,
'from-name' => $me['name'],
@ -232,14 +231,14 @@ class Mail
foreach ($images as $image) {
$image_rid = Photo::ridFromURI($image);
if (!empty($image_rid)) {
Photo::update(['allow-cid' => '<' . $recipient . '>'], ['resource-id' => $image_rid, 'album' => 'Wall Photos', 'uid' => local_user()]);
Photo::update(['allow-cid' => '<' . $recipient . '>'], ['resource-id' => $image_rid, 'album' => 'Wall Photos', 'uid' => DI::userSession()->getLocalUserId()]);
}
}
}
}
if ($post_id) {
Worker::add(PRIORITY_HIGH, "Notifier", Delivery::MAIL, $post_id);
Worker::add(Worker::PRIORITY_HIGH, "Notifier", Delivery::MAIL, $post_id);
return intval($post_id);
} else {
return -3;

View file

@ -25,6 +25,7 @@ use Friendica\Core\Addon;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Item;
use stdClass;
/**
@ -62,8 +63,8 @@ class Nodeinfo
$logger->info('user statistics', $userStats);
$posts = DBA::count('post-thread', ["`uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE NOT `deleted` AND `origin`)"]);
$comments = DBA::count('post', ["NOT `deleted` AND `gravity` = ? AND `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `origin`)", GRAVITY_COMMENT]);
$config->set('nodeinfo', 'local_posts', $posts);
$comments = DBA::count('post', ["NOT `deleted` AND `gravity` = ? AND `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `origin`)", Item::GRAVITY_COMMENT]);
$config->set('nodeinfo', 'local_posts', $posts);
$config->set('nodeinfo', 'local_comments', $comments);
$logger->info('User actitivy', ['posts' => $posts, 'comments' => $comments]);
@ -79,19 +80,17 @@ class Nodeinfo
$config = DI::config();
$usage = new stdClass();
$usage->users = [];
$usage->users = new \stdClass;
if (!empty($config->get('system', 'nodeinfo'))) {
$usage->users = [
'total' => intval($config->get('nodeinfo', 'total_users')),
'activeHalfyear' => intval($config->get('nodeinfo', 'active_users_halfyear')),
'activeMonth' => intval($config->get('nodeinfo', 'active_users_monthly'))
];
$usage->users->total = intval($config->get('nodeinfo', 'total_users'));
$usage->users->activeHalfyear = intval($config->get('nodeinfo', 'active_users_halfyear'));
$usage->users->activeMonth = intval($config->get('nodeinfo', 'active_users_monthly'));
$usage->localPosts = intval($config->get('nodeinfo', 'local_posts'));
$usage->localComments = intval($config->get('nodeinfo', 'local_comments'));
if ($version2) {
$usage->users['activeWeek'] = intval($config->get('nodeinfo', 'active_users_weekly'));
$usage->users->activeWeek = intval($config->get('nodeinfo', 'active_users_weekly'));
}
}
@ -163,25 +162,16 @@ class Nodeinfo
*
* @param IManageConfigValues $config Configuration instance
* @return array Organization information
* @throws \Exception
*/
public static function getOrganization(IManageConfigValues $config): array
{
$organization = [
'name' => null,
'contact' => null,
'account' => null
$administrator = User::getFirstAdmin(['username', 'email', 'nickname']);
return [
'name' => $administrator['username'] ?? null,
'contact' => $administrator['email'] ?? null,
'account' => $administrator['nickname'] ?? '' ? DI::baseUrl()->get() . '/profile/' . $administrator['nickname'] : null,
];
if (!empty($config->get('config', 'admin_email'))) {
$adminList = explode(',', str_replace(' ', '', $config->get('config', 'admin_email')));
$organization['contact'] = $adminList[0];
$administrator = User::getByEmail($adminList[0], ['username', 'nickname']);
if (!empty($administrator)) {
$organization['name'] = $administrator['username'];
$organization['account'] = DI::baseUrl()->get() . '/profile/' . $administrator['nickname'];
}
}
return $organization;
}
}

View file

@ -25,7 +25,6 @@ use Friendica\Core\Cache\Enum\Duration;
use Friendica\Core\Logger;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use Friendica\DI;
use Friendica\Core\Storage\Type\ExternalResource;
use Friendica\Core\Storage\Exception\InvalidClassStorageException;
@ -174,6 +173,64 @@ class Photo
return $photo;
}
/**
* Returns all browsable albums for a given user
*
* @param int $uid The given user
*
* @return array An array of albums
* @throws \Exception
*/
public static function getBrowsableAlbumsForUser(int $uid): array
{
$photos = DBA::toArray(
DBA::p(
"SELECT DISTINCT(`album`) AS `album` FROM `photo` WHERE `uid` = ? AND NOT `photo-type` IN (?, ?)",
$uid,
static::CONTACT_AVATAR,
static::CONTACT_BANNER
)
);
return array_column($photos, 'album');
}
/**
* Returns browsable photos for a given user (optional and a given album)
*
* @param int $uid The given user id
* @param string|null $album (optional) The given album
*
* @return array All photos of the user/album
* @throws \Exception
*/
public static function getBrowsablePhotosForUser(int $uid, string $album = null): array
{
$values = [
$uid,
Photo::CONTACT_AVATAR,
Photo::CONTACT_BANNER
];
if (!empty($album)) {
$sqlExtra = "AND `album` = ? ";
$values[] = $album;
$sqlExtra2 = "";
} else {
$sqlExtra = '';
$sqlExtra2 = ' ORDER BY created DESC LIMIT 0, 10';
}
return DBA::toArray(
DBA::p(
"SELECT `resource-id`, ANY_VALUE(`id`) AS `id`, ANY_VALUE(`filename`) AS `filename`, ANY_VALUE(`type`) AS `type`,
min(`scale`) AS `hiq`, max(`scale`) AS `loq`, ANY_VALUE(`desc`) AS `desc`, ANY_VALUE(`created`) AS `created`
FROM `photo` WHERE `uid` = ? AND NOT `photo-type` IN (?, ?) $sqlExtra
GROUP BY `resource-id` $sqlExtra2",
$values
));
}
/**
* Check if photo with given conditions exists
*
@ -289,11 +346,14 @@ class Photo
* @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.
* @param string $blurhash The blurhash that will be used to generate a picture when the original picture can't be fetched
* @param int $width Image width
* @param int $height Image height
*
* @return array
* @throws \Exception
*/
public static function createPhotoForExternalResource(string $url, int $uid = 0, string $mimetype = ''): array
public static function createPhotoForExternalResource(string $url, int $uid = 0, string $mimetype = '', string $blurhash = null, int $width = null, int $height = null): array
{
if (empty($mimetype)) {
$mimetype = Images::guessTypeByExtension($url);
@ -307,6 +367,9 @@ class Photo
$photo['backend-ref'] = json_encode(['url' => $url, 'uid' => $uid]);
$photo['type'] = $mimetype;
$photo['cacheable'] = true;
$photo['blurhash'] = $blurhash;
$photo['width'] = $width;
$photo['height'] = $height;
return $photo;
}
@ -379,6 +442,7 @@ class Photo
'height' => $image->getHeight(),
'width' => $image->getWidth(),
'datasize' => strlen($image->asString()),
'blurhash' => $image->getBlurHash(),
'data' => $data,
'scale' => $scale,
'photo-type' => $type,
@ -518,8 +582,9 @@ class Photo
$image->scaleToSquare(300);
$filesize = strlen($image->asString());
$maximagesize = DI::config()->get('system', 'maximagesize');
if (!empty($maximagesize) && ($filesize > $maximagesize)) {
$maximagesize = Strings::getBytesFromShorthand(DI::config()->get('system', 'maximagesize'));
if ($maximagesize && ($filesize > $maximagesize)) {
Logger::info('Avatar exceeds image limit', ['uid' => $uid, 'cid' => $cid, 'maximagesize' => $maximagesize, 'size' => $filesize, 'type' => $image->getType()]);
if ($image->getType() == 'image/gif') {
$image->toStatic();
@ -639,10 +704,10 @@ class Photo
{
$sql_extra = Security::getPermissionsSQLByUserId($uid);
$avatar_type = (local_user() && (local_user() == $uid)) ? self::USER_AVATAR : self::DEFAULT;
$banner_type = (local_user() && (local_user() == $uid)) ? self::USER_BANNER : self::DEFAULT;
$avatar_type = (DI::userSession()->getLocalUserId() && (DI::userSession()->getLocalUserId() == $uid)) ? self::USER_AVATAR : self::DEFAULT;
$banner_type = (DI::userSession()->getLocalUserId() && (DI::userSession()->getLocalUserId() == $uid)) ? self::USER_BANNER : self::DEFAULT;
$key = 'photo_albums:' . $uid . ':' . local_user() . ':' . remote_user();
$key = 'photo_albums:' . $uid . ':' . DI::userSession()->getLocalUserId() . ':' . DI::userSession()->getRemoteUserId();
$albums = DI::cache()->get($key);
if (is_null($albums) || $update) {
@ -681,7 +746,7 @@ class Photo
*/
public static function clearAlbumCache(int $uid)
{
$key = 'photo_albums:' . $uid . ':' . local_user() . ':' . remote_user();
$key = 'photo_albums:' . $uid . ':' . DI::userSession()->getLocalUserId() . ':' . DI::userSession()->getRemoteUserId();
DI::cache()->set($key, null, Duration::DAY);
}
@ -909,9 +974,9 @@ class Photo
$width = $image->getWidth();
$height = $image->getHeight();
$maximagesize = DI::config()->get('system', 'maximagesize');
$maximagesize = Strings::getBytesFromShorthand(DI::config()->get('system', 'maximagesize'));
if (!empty($maximagesize) && ($filesize > $maximagesize)) {
if ($maximagesize && ($filesize > $maximagesize)) {
// Scale down to multiples of 640 until the maximum size isn't exceeded anymore
foreach ([5120, 2560, 1280, 640] as $pixels) {
if (($filesize > $maximagesize) && (max($width, $height) > $pixels)) {
@ -1131,8 +1196,8 @@ class Photo
$picture['height'] = $photo['height'];
$picture['type'] = $photo['type'];
$picture['albumpage'] = DI::baseUrl() . '/photos/' . $user['nickname'] . '/image/' . $resource_id;
$picture['picture'] = DI::baseUrl() . '/photo/{$resource_id}-0.' . $image->getExt();
$picture['preview'] = DI::baseUrl() . '/photo/{$resource_id}-{$smallest}.' . $image->getExt();
$picture['picture'] = DI::baseUrl() . '/photo/' . $resource_id . '-0.' . $image->getExt();
$picture['preview'] = DI::baseUrl() . '/photo/' . $resource_id . '-' . $smallest . '.' . $image->getExt();
Logger::info('upload done', ['picture' => $picture]);
return $picture;
@ -1260,7 +1325,7 @@ class Photo
logger::warning('profile banner upload with scale 3 (960) failed');
}
logger::info('new profile banner upload ended');
logger::info('new profile banner upload ended', ['uid' => $uid, 'resource_id' => $resource_id, 'filename' => $filename]);
$condition = ["`photo-type` = ? AND `resource-id` != ? AND `uid` = ?", self::USER_BANNER, $resource_id, $uid];
self::update(['photo-type' => self::DEFAULT], $condition);
@ -1273,4 +1338,3 @@ class Photo
return $resource_id;
}
}

View file

@ -26,7 +26,6 @@ use Friendica\Core\Logger;
use Friendica\Core\System;
use Friendica\Database\Database;
use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use Friendica\DI;
use Friendica\Protocol\Activity;
@ -103,26 +102,25 @@ class Post
}
/**
* Fills an array with data from an post query
* Fills an array with data from a post query
*
* @param object $stmt statement object
* @param bool $do_close
* @param object|bool $stmt Return value from Database->select
* @return array Data array
* @todo Find proper type-hint for $stmt and maybe avoid boolean
* @throws \Exception
*/
public static function toArray($stmt, bool $do_close = true)
public static function toArray($stmt): array
{
if (is_bool($stmt)) {
return $stmt;
return [];
}
$data = [];
while ($row = self::fetch($stmt)) {
$data[] = $row;
}
if ($do_close) {
DBA::close($stmt);
}
DBA::close($stmt);
return $data;
}
@ -376,6 +374,21 @@ class Post
return self::selectView('post-thread-user-view', $selected, $condition, $params);
}
/**
* Select rows from the post-thread-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 selectPostThread(array $selected = [], array $condition = [], array $params = [])
{
return self::selectView('post-thread-view', $selected, $condition, $params);
}
/**
* Select rows from the given view for a given user
*
@ -405,12 +418,12 @@ class Post
AND NOT `owner-id` IN (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `blocked` AND `cid` = `owner-id`)
AND NOT (`gravity` = ? AND `author-id` IN (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `ignored` AND `cid` = `author-id`))
AND NOT (`gravity` = ? AND `owner-id` IN (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `ignored` AND `cid` = `owner-id`))",
0, Contact::SHARING, Contact::FRIEND, GRAVITY_PARENT, 0, $uid, $uid, $uid, GRAVITY_PARENT, $uid, GRAVITY_PARENT, $uid]);
0, Contact::SHARING, Contact::FRIEND, Item::GRAVITY_PARENT, 0, $uid, $uid, $uid, Item::GRAVITY_PARENT, $uid, Item::GRAVITY_PARENT, $uid]);
$select_string = implode(', ', array_map([DBA::class, 'quoteIdentifier'], $selected));
$condition_string = DBA::buildCondition($condition);
$param_string = DBA::buildParameter($params);
$param_string = DBA::buildParameter($params);
$sql = "SELECT " . $select_string . " FROM `" . $view . "` " . $condition_string . $param_string;
$sql = DBA::cleanQuery($sql);
@ -507,7 +520,7 @@ class Post
{
$affected = 0;
Logger::info('Start Update', ['fields' => $fields, 'condition' => $condition, 'uid' => local_user(),'callstack' => System::callstack(10)]);
Logger::info('Start Update', ['fields' => $fields, 'condition' => $condition, 'uid' => DI::userSession()->getLocalUserId(),'callstack' => System::callstack(10)]);
// Don't allow changes to fields that are responsible for the relation between the records
unset($fields['id']);
@ -520,7 +533,7 @@ class Post
unset($fields['parent-uri']);
unset($fields['parent-uri-id']);
$thread_condition = DBA::mergeConditions($condition, ['gravity' => GRAVITY_PARENT]);
$thread_condition = DBA::mergeConditions($condition, ['gravity' => Item::GRAVITY_PARENT]);
// To ensure the data integrity we do it in an transaction
DBA::transaction();
@ -528,7 +541,7 @@ class Post
$update_fields = DI::dbaDefinition()->truncateFieldsForTable('post-user', $fields);
if (!empty($update_fields)) {
$affected_count = 0;
$posts = DBA::select('post-user-view', ['post-user-id'], $condition);
$posts = DBA::select('post-user-view', ['post-user-id'], $condition);
while ($rows = DBA::toArray($posts, false, 100)) {
$puids = array_column($rows, 'post-user-id');
if (!DBA::update('post-user', $update_fields, ['id' => $puids])) {
@ -545,7 +558,7 @@ class Post
$update_fields = DI::dbaDefinition()->truncateFieldsForTable('post-content', $fields);
if (!empty($update_fields)) {
$affected_count = 0;
$posts = DBA::select('post-user-view', ['uri-id'], $condition, ['group_by' => ['uri-id']]);
$posts = DBA::select('post-user-view', ['uri-id'], $condition, ['group_by' => ['uri-id']]);
while ($rows = DBA::toArray($posts, false, 100)) {
$uriids = array_column($rows, 'uri-id');
if (!DBA::update('post-content', $update_fields, ['uri-id' => $uriids])) {
@ -562,7 +575,7 @@ class Post
$update_fields = DI::dbaDefinition()->truncateFieldsForTable('post', $fields);
if (!empty($update_fields)) {
$affected_count = 0;
$posts = DBA::select('post-user-view', ['uri-id'], $condition, ['group_by' => ['uri-id']]);
$posts = DBA::select('post-user-view', ['uri-id'], $condition, ['group_by' => ['uri-id']]);
while ($rows = DBA::toArray($posts, false, 100)) {
$uriids = array_column($rows, 'uri-id');
@ -585,7 +598,7 @@ class Post
$update_fields = Post\DeliveryData::extractFields($fields);
if (!empty($update_fields)) {
$affected_count = 0;
$posts = DBA::select('post-user-view', ['uri-id'], $condition, ['group_by' => ['uri-id']]);
$posts = DBA::select('post-user-view', ['uri-id'], $condition, ['group_by' => ['uri-id']]);
while ($rows = DBA::toArray($posts, false, 100)) {
$uriids = array_column($rows, 'uri-id');
if (!DBA::update('post-delivery-data', $update_fields, ['uri-id' => $uriids])) {
@ -602,7 +615,7 @@ class Post
$update_fields = DI::dbaDefinition()->truncateFieldsForTable('post-thread', $fields);
if (!empty($update_fields)) {
$affected_count = 0;
$posts = DBA::select('post-user-view', ['uri-id'], $thread_condition, ['group_by' => ['uri-id']]);
$posts = DBA::select('post-user-view', ['uri-id'], $thread_condition, ['group_by' => ['uri-id']]);
while ($rows = DBA::toArray($posts, false, 100)) {
$uriids = array_column($rows, 'uri-id');
if (!DBA::update('post-thread', $update_fields, ['uri-id' => $uriids])) {
@ -619,7 +632,7 @@ class Post
$update_fields = DI::dbaDefinition()->truncateFieldsForTable('post-thread-user', $fields);
if (!empty($update_fields)) {
$affected_count = 0;
$posts = DBA::select('post-user-view', ['post-user-id'], $thread_condition);
$posts = DBA::select('post-user-view', ['post-user-id'], $thread_condition);
while ($rows = DBA::toArray($posts, false, 100)) {
$thread_puids = array_column($rows, 'post-user-id');
if (!DBA::update('post-thread-user', $update_fields, ['post-user-id' => $thread_puids])) {

View file

@ -38,12 +38,6 @@ class Delayed
* This is used for automated scheduled posts via feeds or from the API.
*/
const PREPARED = 0;
/**
* The content is posted like a manual post. Means some processing of body will be done.
* Also it is posted with default permissions and default connector settings.
* This is used for mirrored connector posts.
*/
const UNPREPARED = 1;
/**
* Like PREPARED, but additionally the connector settings can differ.
* This is used when manually publishing scheduled posts.
@ -80,7 +74,7 @@ class Delayed
Logger::notice('Adding post for delayed publishing', ['uid' => $item['uid'], 'delayed' => $delayed, 'uri' => $uri]);
$wid = Worker::add(['priority' => PRIORITY_HIGH, 'delayed' => $delayed], 'DelayedPublish', $item, $notify, $taglist, $attachments, $preparation_mode, $uri);
$wid = Worker::add(['priority' => Worker::PRIORITY_HIGH, 'delayed' => $delayed], 'DelayedPublish', $item, $notify, $taglist, $attachments, $preparation_mode, $uri);
if (!$wid) {
return 0;
}
@ -199,37 +193,9 @@ class Delayed
$item['attachments'] = $attachments;
}
if ($preparation_mode == self::UNPREPARED) {
$_SESSION['authenticated'] = true;
$_SESSION['uid'] = $item['uid'];
$_REQUEST = $item;
$_REQUEST['api_source'] = true;
$_REQUEST['profile_uid'] = $item['uid'];
$_REQUEST['title'] = $item['title'] ?? '';
if (!empty($item['app'])) {
$_REQUEST['source'] = $item['app'];
}
require_once 'mod/item.php';
$id = item_post(DI::app());
if (empty($uri) && !empty($item['extid'])) {
$uri = $item['extid'];
}
Logger::notice('Unprepared post stored', ['id' => $id, 'uid' => $item['uid'], 'uri' => $uri]);
if (self::exists($uri, $item['uid'])) {
self::delete($uri, $item['uid']);
}
return $id;
}
$id = Item::insert($item, $notify, $preparation_mode == self::PREPARED);
Logger::notice('Post stored', ['id' => $id, 'uid' => $item['uid'], 'cid' => $item['contact-id']]);
Logger::notice('Post stored', ['id' => $id, 'uid' => $item['uid'], 'cid' => $item['contact-id'] ?? 'N/A']);
if (empty($uri) && !empty($item['uri'])) {
$uri = $item['uri'];

View file

@ -23,10 +23,12 @@ namespace Friendica\Model\Post;
use Friendica\Content\Text\BBCode;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Database\Database;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Item;
use Friendica\Model\Photo;
use Friendica\Model\Post;
@ -56,12 +58,15 @@ class Media
const HTML = 17;
const XML = 18;
const PLAIN = 19;
const ACTIVITY = 20;
const ACCOUNT = 21;
const DOCUMENT = 128;
/**
* Insert a post-media record
*
* @param array $media
* @param bool $force
* @return void
*/
public static function insert(array $media, bool $force = false)
@ -113,7 +118,7 @@ class Media
*/
private static function unsetEmptyFields(array $media): array
{
$fields = ['mimetype', 'height', 'width', 'size', 'preview', 'preview-height', 'preview-width', 'description'];
$fields = ['mimetype', 'height', 'width', 'size', 'preview', 'preview-height', 'preview-width', 'blurhash', 'description'];
foreach ($fields as $field) {
if (empty($media[$field])) {
unset($media[$field]);
@ -199,6 +204,7 @@ class Media
$media['size'] = $imagedata['size'];
$media['width'] = $imagedata[0];
$media['height'] = $imagedata[1];
$media['blurhash'] = $imagedata['blurhash'] ?? null;
} else {
Logger::notice('No image data', ['media' => $media]);
}
@ -215,20 +221,142 @@ class Media
$media = self::addType($media);
}
if ($media['type'] == self::HTML) {
$data = ParseUrl::getSiteinfoCached($media['url'], false);
$media['preview'] = $data['images'][0]['src'] ?? null;
$media['preview-height'] = $data['images'][0]['height'] ?? null;
$media['preview-width'] = $data['images'][0]['width'] ?? null;
$media['description'] = $data['text'] ?? null;
$media['name'] = $data['title'] ?? null;
$media['author-url'] = $data['author_url'] ?? null;
$media['author-name'] = $data['author_name'] ?? null;
$media['author-image'] = $data['author_img'] ?? null;
$media['publisher-url'] = $data['publisher_url'] ?? null;
$media['publisher-name'] = $data['publisher_name'] ?? null;
$media['publisher-image'] = $data['publisher_img'] ?? null;
if (in_array($media['type'], [self::TEXT, self::APPLICATION, self::HTML, self::XML, self::PLAIN])) {
$media = self::addActivity($media);
}
if (in_array($media['type'], [self::TEXT, self::APPLICATION, self::HTML, self::XML, self::PLAIN])) {
$media = self::addAccount($media);
}
if ($media['type'] == self::HTML) {
$media = self::addPage($media);
}
return $media;
}
/**
* Adds the activity type if the media entry is linked to an activity
*
* @param array $media
* @return array
*/
private static function addActivity(array $media): array
{
$id = Item::fetchByLink($media['url']);
if (empty($id)) {
return $media;
}
$item = Post::selectFirst([], ['id' => $id, 'network' => Protocol::FEDERATED]);
if (empty($item['id'])) {
Logger::debug('Not a federated activity', ['id' => $id, 'uri-id' => $media['uri-id'], 'url' => $media['url']]);
return $media;
}
if (!empty($item['plink']) && Strings::compareLink($item['plink'], $media['url']) &&
parse_url($item['plink'], PHP_URL_HOST) != parse_url($item['uri'], PHP_URL_HOST)) {
Logger::debug('Not a link to an activity', ['uri-id' => $media['uri-id'], 'url' => $media['url'], 'plink' => $item['plink'], 'uri' => $item['uri']]);
return $media;
}
if (in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN])) {
$media['mimetype'] = 'application/activity+json';
} elseif ($item['network'] == Protocol::DIASPORA) {
$media['mimetype'] = 'application/xml';
}
$contact = Contact::getById($item['author-id'], ['avatar', 'gsid']);
if (!empty($contact['gsid'])) {
$gserver = DBA::selectFirst('gserver', ['url', 'site_name'], ['id' => $contact['gsid']]);
}
$media['type'] = self::ACTIVITY;
$media['media-uri-id'] = $item['uri-id'];
$media['height'] = null;
$media['width'] = null;
$media['preview'] = null;
$media['preview-height'] = null;
$media['preview-width'] = null;
$media['blurhash'] = null;
$media['description'] = $item['body'];
$media['name'] = $item['title'];
$media['author-url'] = $item['author-link'];
$media['author-name'] = $item['author-name'];
$media['author-image'] = $contact['avatar'] ?? $item['author-avatar'];
$media['publisher-url'] = $gserver['url'] ?? null;
$media['publisher-name'] = $gserver['site_name'] ?? null;
$media['publisher-image'] = null;
Logger::debug('Activity detected', ['uri-id' => $media['uri-id'], 'url' => $media['url'], 'plink' => $item['plink'], 'uri' => $item['uri']]);
return $media;
}
/**
* Adds the account type if the media entry is linked to an account
*
* @param array $media
* @return array
*/
private static function addAccount(array $media): array
{
$contact = Contact::getByURL($media['url'], false);
if (empty($contact) || ($contact['network'] == Protocol::PHANTOM)) {
return $media;
}
if (in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN])) {
$media['mimetype'] = 'application/activity+json';
}
if (!empty($contact['gsid'])) {
$gserver = DBA::selectFirst('gserver', ['url', 'site_name'], ['id' => $contact['gsid']]);
}
$media['type'] = self::ACCOUNT;
$media['media-uri-id'] = $contact['uri-id'];
$media['height'] = null;
$media['width'] = null;
$media['preview'] = null;
$media['preview-height'] = null;
$media['preview-width'] = null;
$media['blurhash'] = null;
$media['description'] = $contact['about'];
$media['name'] = $contact['name'];
$media['author-url'] = $contact['url'];
$media['author-name'] = $contact['name'];
$media['author-image'] = $contact['avatar'];
$media['publisher-url'] = $gserver['url'] ?? null;
$media['publisher-name'] = $gserver['site_name'] ?? null;
$media['publisher-image'] = null;
Logger::debug('Account detected', ['uri-id' => $media['uri-id'], 'url' => $media['url'], 'uri' => $contact['url']]);
return $media;
}
/**
* Add page infos for HTML entries
*
* @param array $media
* @return array
*/
private static function addPage(array $media): array
{
$data = ParseUrl::getSiteinfoCached($media['url'], false);
$media['preview'] = $data['images'][0]['src'] ?? null;
$media['preview-height'] = $data['images'][0]['height'] ?? null;
$media['preview-width'] = $data['images'][0]['width'] ?? null;
$media['blurhash'] = $data['images'][0]['blurhash'] ?? null;
$media['description'] = $data['text'] ?? null;
$media['name'] = $data['title'] ?? null;
$media['author-url'] = $data['author_url'] ?? null;
$media['author-name'] = $data['author_name'] ?? null;
$media['author-image'] = $data['author_img'] ?? null;
$media['publisher-url'] = $data['publisher_url'] ?? null;
$media['publisher-name'] = $data['publisher_name'] ?? null;
$media['publisher-image'] = $data['publisher_img'] ?? null;
return $media;
}
@ -248,6 +376,7 @@ class Media
$media['size'] = $photo['datasize'];
$media['width'] = $photo['width'];
$media['height'] = $photo['height'];
$media['blurhash'] = $photo['blurhash'];
}
if (!preg_match('|.*?/photo/(.*[a-fA-F0-9])\-(.*[0-9])\..*[\w]|', $media['preview'] ?? '', $matches)) {
@ -332,19 +461,14 @@ class Media
* @param string $body
* @return string Body without media links
*/
public static function insertFromBody(int $uriid, string $body): string
public static function insertFromBody(int $uriid, string $body, bool $endmatch = false): string
{
$endmatchpattern = $endmatch ? '\z' : '';
// Simplify image codes
$unshared_body = $body = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $body);
// Only remove the shared data from "real" reshares
$shared = BBCode::fetchShareAttributes($body);
if (!empty($shared['guid'])) {
$unshared_body = preg_replace("/\s*\[share .*?\].*?\[\/share\]\s*/ism", '', $body);
}
$unshared_body = $body = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]$endmatchpattern/ism", '[img]$3[/img]', $body);
$attachments = [];
if (preg_match_all("#\[url=([^\]]+?)\]\s*\[img=([^\[\]]*)\]([^\[\]]*)\[\/img\]\s*\[/url\]#ism", $body, $pictures, PREG_SET_ORDER)) {
if (preg_match_all("#\[url=([^\]]+?)\]\s*\[img=([^\[\]]*)\]([^\[\]]*)\[\/img\]\s*\[/url\]$endmatchpattern#ism", $body, $pictures, PREG_SET_ORDER)) {
foreach ($pictures as $picture) {
if (!self::isPictureLink($picture[1], $picture[2])) {
continue;
@ -356,14 +480,14 @@ class Media
}
}
if (preg_match_all("/\[img=([^\[\]]*)\]([^\[\]]*)\[\/img\]/Usi", $body, $pictures, PREG_SET_ORDER)) {
if (preg_match_all("/\[img=([^\[\]]*)\]([^\[\]]*)\[\/img\]$endmatchpattern/Usi", $body, $pictures, PREG_SET_ORDER)) {
foreach ($pictures as $picture) {
$body = str_replace($picture[0], '', $body);
$attachments[$picture[1]] = ['uri-id' => $uriid, 'type' => self::IMAGE, 'url' => $picture[1], 'description' => $picture[2]];
}
}
if (preg_match_all("#\[url=([^\]]+?)\]\s*\[img\]([^\[]+?)\[/img\]\s*\[/url\]#ism", $body, $pictures, PREG_SET_ORDER)) {
if (preg_match_all("#\[url=([^\]]+?)\]\s*\[img\]([^\[]+?)\[/img\]\s*\[/url\]$endmatchpattern#ism", $body, $pictures, PREG_SET_ORDER)) {
foreach ($pictures as $picture) {
if (!self::isPictureLink($picture[1], $picture[2])) {
continue;
@ -375,41 +499,58 @@ class Media
}
}
if (preg_match_all("/\[img\]([^\[\]]*)\[\/img\]/ism", $body, $pictures, PREG_SET_ORDER)) {
if (preg_match_all("/\[img\]([^\[\]]*)\[\/img\]$endmatchpattern/ism", $body, $pictures, PREG_SET_ORDER)) {
foreach ($pictures as $picture) {
$body = str_replace($picture[0], '', $body);
$attachments[$picture[1]] = ['uri-id' => $uriid, 'type' => self::IMAGE, 'url' => $picture[1]];
}
}
if (preg_match_all("/\[audio\]([^\[\]]*)\[\/audio\]/ism", $body, $audios, PREG_SET_ORDER)) {
if (preg_match_all("/\[audio\]([^\[\]]*)\[\/audio\]$endmatchpattern/ism", $body, $audios, PREG_SET_ORDER)) {
foreach ($audios as $audio) {
$body = str_replace($audio[0], '', $body);
$attachments[$audio[1]] = ['uri-id' => $uriid, 'type' => self::AUDIO, 'url' => $audio[1]];
}
}
if (preg_match_all("/\[video\]([^\[\]]*)\[\/video\]/ism", $body, $videos, PREG_SET_ORDER)) {
if (preg_match_all("/\[video\]([^\[\]]*)\[\/video\]$endmatchpattern/ism", $body, $videos, PREG_SET_ORDER)) {
foreach ($videos as $video) {
$body = str_replace($video[0], '', $body);
$attachments[$video[1]] = ['uri-id' => $uriid, 'type' => self::VIDEO, 'url' => $video[1]];
}
}
foreach ($attachments as $attachment) {
if (Post\Link::exists($uriid, $attachment['preview'] ?? $attachment['url'])) {
continue;
}
if ($uriid != 0) {
foreach ($attachments as $attachment) {
if (Post\Link::exists($uriid, $attachment['preview'] ?? $attachment['url'])) {
continue;
}
// Only store attachments that are part of the unshared body
if (Item::containsLink($unshared_body, $attachment['preview'] ?? $attachment['url'], $attachment['type'])) {
self::insert($attachment);
// Only store attachments that are part of the unshared body
if (Item::containsLink($unshared_body, $attachment['preview'] ?? $attachment['url'], $attachment['type'])) {
self::insert($attachment);
}
}
}
return trim($body);
}
/**
* Remove media that is at the end of the body
*
* @param string $body
* @return string
*/
public static function removeFromEndOfBody(string $body): string
{
do {
$prebody = $body;
$body = self::insertFromBody(0, $body, true);
} while ($prebody != $body);
return $body;
}
/**
* Add media links from a relevant url in the body
*
@ -417,15 +558,8 @@ class Media
* @param string $body
* @return void
*/
public static function insertFromRelevantUrl(int $uriid, string $body)
public static function insertFromRelevantUrl(int $uriid, string $body, string $fullbody, string $network)
{
// Only remove the shared data from "real" reshares
$shared = BBCode::fetchShareAttributes($body);
if (!empty($shared['guid'])) {
// Don't look at the shared content
$body = preg_replace("/\s*\[share .*?\].*?\[\/share\]\s*/ism", '', $body);
}
// Remove all hashtags and mentions
$body = preg_replace("/([#@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '', $body);
@ -433,7 +567,10 @@ class Media
if (preg_match_all("/\[url\](https?:.*?)\[\/url\]/ism", $body, $matches)) {
foreach ($matches[1] as $url) {
Logger::info('Got page url (link without description)', ['uri-id' => $uriid, 'url' => $url]);
self::insert(['uri-id' => $uriid, 'type' => self::UNKNOWN, 'url' => $url]);
self::insert(['uri-id' => $uriid, 'type' => self::UNKNOWN, 'url' => $url], false, $network);
if ($network == Protocol::DFRN) {
self::revertHTMLType($uriid, $url, $fullbody);
}
}
}
@ -441,11 +578,31 @@ class Media
if (preg_match_all("/\[url\=(https?:.*?)\].*?\[\/url\]/ism", $body, $matches)) {
foreach ($matches[1] as $url) {
Logger::info('Got page url (link with description)', ['uri-id' => $uriid, 'url' => $url]);
self::insert(['uri-id' => $uriid, 'type' => self::UNKNOWN, 'url' => $url]);
self::insert(['uri-id' => $uriid, 'type' => self::UNKNOWN, 'url' => $url], false, $network);
if ($network == Protocol::DFRN) {
self::revertHTMLType($uriid, $url, $fullbody);
}
}
}
}
/**
* Revert the media type of links to UNKNOWN for DFRN posts when they aren't attached
*
* @param integer $uriid
* @param string $url
* @param string $body
* @return void
*/
private static function revertHTMLType(int $uriid, string $url, string $body)
{
$attachment = BBCode::getAttachmentData($body);
if (!empty($attachment['url']) && Network::getUrlMatch($attachment['url'], $url)) {
return;
}
DBA::update('post-media', ['type' => self::UNKNOWN], ['uri-id' => $uriid, 'type' => self::HTML, 'url' => $url]);
}
/**
* Add media links from the attachment field
*
@ -455,9 +612,6 @@ class Media
*/
public static function insertFromAttachmentData(int $uriid, string $body)
{
// Don't look at the shared content
$body = preg_replace("/\s*\[share .*?\].*?\[\/share\]\s*/ism", '', $body);
$data = BBCode::getAttachmentData($body);
if (empty($data)) {
return;
@ -517,7 +671,7 @@ class Media
*/
public static function getByURIId(int $uri_id, array $types = [])
{
$condition = ['uri-id' => $uri_id];
$condition = ["`uri-id` = ? AND `type` != ?", $uri_id, self::UNKNOWN];
if (!empty($types)) {
$condition = DBA::mergeConditions($condition, ['type' => $types]);
@ -536,7 +690,7 @@ class Media
*/
public static function existsByURIId(int $uri_id, array $types = []): bool
{
$condition = ['uri-id' => $uri_id];
$condition = ["`uri-id` = ? AND `type` != ?", $uri_id, self::UNKNOWN];
if (!empty($types)) {
$condition = DBA::mergeConditions($condition, ['type' => $types]);
@ -549,12 +703,11 @@ class Media
* Split the attachment media in the three segments "visual", "link" and "additional"
*
* @param int $uri_id URI id
* @param string $guid GUID
* @param array $links list of links that shouldn't be added
* @param bool $has_media
* @return array attachments
*/
public static function splitAttachments(int $uri_id, string $guid = '', array $links = [], bool $has_media = true): array
public static function splitAttachments(int $uri_id, array $links = [], bool $has_media = true): array
{
$attachments = ['visual' => [], 'link' => [], 'additional' => []];
@ -585,11 +738,17 @@ class Media
}
}
// Currently these two types are ignored here.
// Posts are added differently and contacts are not displayed as attachments.
if (in_array($medium['type'], [self::ACCOUNT, self::ACTIVITY])) {
continue;
}
if (!empty($medium['preview'])) {
$previews[] = $medium['preview'];
}
$type = explode('/', current(explode(';', $medium['mimetype'])));
$type = explode('/', explode(';', $medium['mimetype'] ?? '')[0]);
if (count($type) < 2) {
Logger::info('Unknown MimeType', ['type' => $type, 'media' => $medium]);
$filetype = 'unkn';
@ -648,11 +807,13 @@ class Media
/**
* Add media attachments to the body
*
* @param int $uriid
* @param int $uriid
* @param string $body
* @param array $types
*
* @return string body
*/
public static function addAttachmentsToBody(int $uriid, string $body = ''): string
public static function addAttachmentsToBody(int $uriid, string $body = '', array $types = [self::IMAGE, self::AUDIO, self::VIDEO]): string
{
if (empty($body)) {
$item = Post::selectFirst(['body'], ['uri-id' => $uriid]);
@ -665,7 +826,7 @@ class Media
$body = preg_replace("/\s*\[attachment .*?\].*?\[\/attachment\]\s*/ism", '', $body);
foreach (self::getByURIId($uriid, [self::IMAGE, self::AUDIO, self::VIDEO]) as $media) {
foreach (self::getByURIId($uriid, $types) as $media) {
if (Item::containsLink($body, $media['preview'] ?? $media['url'], $media['type'])) {
continue;
}

View file

@ -30,6 +30,7 @@ use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Item;
use Friendica\Model\Post;
use Friendica\Model\Subscription;
use Friendica\Model\Tag;
@ -177,11 +178,15 @@ class UserNotification
return;
}
$user = User::getById($uid, ['account-type']);
$user = User::getById($uid, ['account-type', 'account_removed', 'account_expired']);
if (in_array($user['account-type'], [User::ACCOUNT_TYPE_COMMUNITY, User::ACCOUNT_TYPE_RELAY])) {
return;
}
if ($user['account_removed'] || $user['account_expired']) {
return;
}
$author = Contact::getById($item['author-id'], ['contact-type']);
if (empty($author)) {
return;
@ -288,7 +293,7 @@ class UserNotification
}
// Only create notifications for posts and comments, not for activities
if (($item['gravity'] == GRAVITY_ACTIVITY) && ($item['verb'] != Activity::ANNOUNCE)) {
if (($item['gravity'] == Item::GRAVITY_ACTIVITY) && ($item['verb'] != Activity::ANNOUNCE)) {
return;
}
@ -310,7 +315,7 @@ class UserNotification
*/
private static function insertNotificationByItem(int $type, int $uid, array $item): void
{
if (($item['verb'] != Activity::ANNOUNCE) && ($item['gravity'] == GRAVITY_ACTIVITY) &&
if (($item['verb'] != Activity::ANNOUNCE) && ($item['gravity'] == Item::GRAVITY_ACTIVITY) &&
!in_array($type, [self::TYPE_DIRECT_COMMENT, self::TYPE_DIRECT_THREAD_COMMENT])) {
// Activities are only stored when performed on the user's post or comment
return;
@ -321,7 +326,7 @@ class UserNotification
$item['vid'],
$type,
$item['author-id'],
$item['gravity'] == GRAVITY_ACTIVITY ? $item['thr-parent-id'] : $item['uri-id'],
$item['gravity'] == Item::GRAVITY_ACTIVITY ? $item['thr-parent-id'] : $item['uri-id'],
$item['parent-uri-id']
);
@ -423,14 +428,14 @@ class UserNotification
private static function checkShared(array $item, int $uid): bool
{
// Only check on original posts and reshare ("announce") activities, otherwise return
if (($item['gravity'] != GRAVITY_PARENT) && ($item['verb'] != Activity::ANNOUNCE)) {
if (($item['gravity'] != Item::GRAVITY_PARENT) && ($item['verb'] != Activity::ANNOUNCE)) {
return false;
}
// Don't notify about reshares by communities of our own posts or each time someone comments
if (($item['verb'] == Activity::ANNOUNCE) && DBA::exists('contact', ['id' => $item['contact-id'], 'contact-type' => Contact::TYPE_COMMUNITY])) {
$post = Post::selectFirst(['origin', 'gravity'], ['uri-id' => $item['thr-parent-id'], 'uid' => $uid]);
if ($post['origin'] || ($post['gravity'] != GRAVITY_PARENT)) {
if (!$post || $post['origin'] || ($post['gravity'] != Item::GRAVITY_PARENT)) {
return false;
}
}
@ -497,7 +502,7 @@ class UserNotification
*/
private static function checkCommentedThread(array $item, array $contacts): bool
{
$condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_PARENT];
$condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => Item::GRAVITY_PARENT];
return Post::exists($condition);
}
@ -511,7 +516,7 @@ class UserNotification
*/
private static function checkDirectComment(array $item, array $contacts): bool
{
$condition = ['uri' => $item['thr-parent'], 'uid' => $item['uid'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_COMMENT];
$condition = ['uri' => $item['thr-parent'], 'uid' => $item['uid'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => Item::GRAVITY_COMMENT];
return Post::exists($condition);
}
@ -525,7 +530,7 @@ class UserNotification
*/
private static function checkDirectCommentedThread(array $item, array $contacts): bool
{
$condition = ['uri' => $item['thr-parent'], 'uid' => $item['uid'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_PARENT];
$condition = ['uri' => $item['thr-parent'], 'uid' => $item['uid'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => Item::GRAVITY_PARENT];
return Post::exists($condition);
}
@ -539,7 +544,7 @@ class UserNotification
*/
private static function checkCommentedParticipation(array $item, array $contacts): bool
{
$condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_COMMENT];
$condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => Item::GRAVITY_COMMENT];
return Post::exists($condition);
}
@ -553,7 +558,7 @@ class UserNotification
*/
private static function checkFollowParticipation(array $item, array $contacts): bool
{
$condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_ACTIVITY, 'verb' => Activity::FOLLOW];
$condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => Item::GRAVITY_ACTIVITY, 'verb' => Activity::FOLLOW];
return Post::exists($condition);
}
@ -567,7 +572,7 @@ class UserNotification
*/
private static function checkActivityParticipation(array $item, array $contacts): bool
{
$condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_ACTIVITY];
$condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => Item::GRAVITY_ACTIVITY];
return Post::exists($condition);
}
}

View file

@ -30,7 +30,6 @@ 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;
@ -153,11 +152,11 @@ class Profile
if ($owner['net-publish'] || $force) {
// Update global directory in background
if (Search::getGlobalDirectory()) {
Worker::add(PRIORITY_LOW, 'Directory', $owner['url']);
Worker::add(Worker::PRIORITY_LOW, 'Directory', $owner['url']);
}
}
Worker::add(PRIORITY_LOW, 'ProfileUpdate', $uid);
Worker::add(Worker::PRIORITY_LOW, 'ProfileUpdate', $uid);
}
/**
@ -239,7 +238,7 @@ class Profile
DI::page()['title'] = $profile['name'] . ' @ ' . DI::config()->get('config', 'sitename');
if (!local_user()) {
if (!DI::userSession()->getLocalUserId()) {
$a->setCurrentTheme($profile['theme']);
$a->setCurrentMobileTheme(DI::pConfig()->get($a->getProfileOwner(), 'system', 'mobile_theme') ?? '');
}
@ -255,7 +254,7 @@ class Profile
require_once $theme_info_file;
}
$block = (DI::config()->get('system', 'block_public') && !Session::isAuthenticated());
$block = (DI::config()->get('system', 'block_public') && !DI::userSession()->isAuthenticated());
/**
* @todo
@ -295,8 +294,8 @@ class Profile
$profile_contact = [];
if (local_user() && ($profile['uid'] ?? 0) != local_user()) {
$profile_contact = Contact::getByURL($profile['nurl'], null, [], local_user());
if (DI::userSession()->getLocalUserId() && ($profile['uid'] ?? 0) != DI::userSession()->getLocalUserId()) {
$profile_contact = Contact::getByURL($profile['nurl'], null, [], DI::userSession()->getLocalUserId());
}
if (!empty($profile['cid']) && self::getMyURL()) {
$profile_contact = Contact::selectFirst([], ['id' => $profile['cid']]);
@ -336,13 +335,13 @@ class Profile
if (!$visitor_is_authenticated) {
// Remote follow is only available for local profiles
if (!empty($profile['nickname']) && strpos($profile_url, DI::baseUrl()->get()) === 0) {
$follow_link = 'remote_follow/' . $profile['nickname'];
$follow_link = 'profile/' . $profile['nickname'] . '/remote_follow';
}
} else {
if ($visitor_is_following) {
$unfollow_link = $visitor_base_path . '/unfollow?url=' . urlencode($profile_url) . '&auto=1';
$unfollow_link = $visitor_base_path . '/contact/unfollow?url=' . urlencode($profile_url) . '&auto=1';
} else {
$follow_link = $visitor_base_path .'/follow?url=' . urlencode($profile_url) . '&auto=1';
$follow_link = $visitor_base_path . '/contact/follow?url=' . urlencode($profile_url) . '&auto=1';
}
}
@ -350,7 +349,7 @@ class Profile
if ($visitor_is_followed || $visitor_is_following) {
$wallmessage_link = $visitor_base_path . '/message/new/' . $profile_contact['id'];
} elseif ($visitor_is_authenticated && !empty($profile['unkmail'])) {
$wallmessage_link = 'wallmessage/' . $profile['nickname'];
$wallmessage_link = 'profile/' . $profile['nickname'] . '/unkmail';
}
}
}
@ -379,7 +378,7 @@ class Profile
$xmpp = !empty($profile['xmpp']) ? DI::l10n()->t('XMPP:') : false;
$matrix = !empty($profile['matrix']) ? DI::l10n()->t('Matrix:') : false;
if ((!empty($profile['hidewall']) || $block) && !Session::isAuthenticated()) {
if ((!empty($profile['hidewall']) || $block) && !DI::userSession()->isAuthenticated()) {
$location = $homepage = $about = false;
}
@ -413,7 +412,7 @@ class Profile
}
if (!$block && $show_contacts) {
$contact_block = ContactBlock::getHTML($profile, local_user());
$contact_block = ContactBlock::getHTML($profile, DI::userSession()->getLocalUserId());
if (is_array($profile) && !$profile['hide-friends']) {
$contact_count = DBA::count('contact', [
@ -452,6 +451,10 @@ class Profile
$p['url'] = Contact::magicLinkById($cid, $profile['url']);
if (!isset($profile['hidewall'])) {
Logger::warning('Missing hidewall key in profile array', ['profile' => $profile, 'callstack' => System::callstack(10)]);
}
$tpl = Renderer::getMarkupTemplate('profile/vcard.tpl');
$o .= Renderer::replaceMacros($tpl, [
'$profile' => $p,
@ -462,12 +465,13 @@ class Profile
'$unfollow' => DI::l10n()->t('Unfollow'),
'$unfollow_link' => $unfollow_link,
'$subscribe_feed' => DI::l10n()->t('Atom feed'),
'$subscribe_feed_link' => $profile['poll'],
'$subscribe_feed_link' => $profile['hidewall'] ?? 0 ? '' : $profile['poll'],
'$wallmessage' => DI::l10n()->t('Message'),
'$wallmessage_link' => $wallmessage_link,
'$account_type' => $account_type,
'$location' => $location,
'$homepage' => $homepage,
'$homepage_verified' => DI::l10n()->t('This website has been verified to belong to the same person.'),
'$about' => $about,
'$network' => DI::l10n()->t('Network:'),
'$contacts' => $contact_count,
@ -493,7 +497,7 @@ class Profile
*/
public static function getBirthdays(): string
{
if (!local_user() || DI::mode()->isMobile() || DI::mode()->isMobile()) {
if (!DI::userSession()->getLocalUserId() || DI::mode()->isMobile() || DI::mode()->isMobile()) {
return '';
}
@ -506,7 +510,7 @@ class Profile
$bd_short = DI::l10n()->t('F d');
$cacheKey = 'get_birthdays:' . local_user();
$cacheKey = 'get_birthdays:' . DI::userSession()->getLocalUserId();
$events = DI::cache()->get($cacheKey);
if (is_null($events)) {
$result = DBA::p(
@ -523,7 +527,7 @@ class Profile
ORDER BY `start`",
Contact::SHARING,
Contact::FRIEND,
local_user(),
DI::userSession()->getLocalUserId(),
DateTimeFormat::utc('now + 6 days'),
DateTimeFormat::utcNow()
);
@ -595,7 +599,7 @@ class Profile
$a = DI::app();
$o = '';
if (!local_user() || DI::mode()->isMobile() || DI::mode()->isMobile()) {
if (!DI::userSession()->getLocalUserId() || DI::mode()->isMobile() || DI::mode()->isMobile()) {
return $o;
}
@ -610,7 +614,7 @@ class Profile
$classtoday = '';
$condition = ["`uid` = ? AND `type` != 'birthday' AND `start` < ? AND `start` >= ?",
local_user(), DateTimeFormat::utc('now + 7 days'), DateTimeFormat::utc('now - 1 days')];
DI::userSession()->getLocalUserId(), DateTimeFormat::utc('now + 7 days'), DateTimeFormat::utc('now - 1 days')];
$s = DBA::select('event', [], $condition, ['order' => ['start']]);
$r = [];
@ -620,7 +624,7 @@ class Profile
$total = 0;
while ($rr = DBA::fetch($s)) {
$condition = ['parent-uri' => $rr['uri'], 'uid' => $rr['uid'], 'author-id' => public_contact(),
$condition = ['parent-uri' => $rr['uri'], 'uid' => $rr['uid'], 'author-id' => DI::userSession()->getPublicContactId(),
'vid' => [Verb::getID(Activity::ATTEND), Verb::getID(Activity::ATTENDMAYBE)],
'visible' => true, 'deleted' => false];
if (!Post::exists($condition)) {
@ -680,10 +684,11 @@ class Profile
* Retrieves the my_url session variable
*
* @return string
* @deprecated since version 2022.12, please use UserSession->getMyUrl instead
*/
public static function getMyURL(): string
{
return Session::get('my_url') ?? '';
return DI::userSession()->getMyUrl();
}
/**
@ -712,7 +717,7 @@ class Profile
$my_url = self::getMyURL();
$my_url = Network::isUrlValid($my_url);
if (empty($my_url) || local_user()) {
if (empty($my_url) || DI::userSession()->getLocalUserId()) {
return;
}
@ -730,7 +735,7 @@ class Profile
$contact = DBA::selectFirst('contact',['id', 'url'], ['id' => $cid]);
if (DBA::isResult($contact) && remote_user() && remote_user() == $contact['id']) {
if (DBA::isResult($contact) && DI::userSession()->getRemoteUserId() && DI::userSession()->getRemoteUserId() == $contact['id']) {
Logger::info('The visitor ' . $my_url . ' is already authenticated');
return;
}
@ -797,7 +802,7 @@ class Profile
$_SESSION['my_url'] = $visitor['url'];
$_SESSION['remote_comment'] = $visitor['subscribe'];
Session::setVisitorsContacts();
DI::userSession()->setVisitorsContacts();
$a->setContactId($visitor['id']);
@ -865,7 +870,7 @@ class Profile
$a->setContactId($arr['visitor']['id']);
info(DI::l10n()->t('OpenWebAuth: %1$s welcomes %2$s', DI::baseUrl()->getHostname(), $visitor['name']));
DI::sysmsg()->addInfo(DI::l10n()->t('OpenWebAuth: %1$s welcomes %2$s', DI::baseUrl()->getHostname(), $visitor['name']));
Logger::info('OpenWebAuth: auth success from ' . $visitor['addr']);
}
@ -916,7 +921,7 @@ class Profile
*/
public static function getThemeUid(App $a): int
{
return local_user() ?: $a->getProfileOwner();
return DI::userSession()->getLocalUserId() ?: $a->getProfileOwner();
}
/**

View file

@ -37,7 +37,7 @@ class PushSubscriber
* @return void
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function publishFeed(int $uid, int $default_priority = PRIORITY_HIGH)
public static function publishFeed(int $uid, int $default_priority = Worker::PRIORITY_HIGH)
{
$condition = ['push' => 0, 'uid' => $uid];
DBA::update('push_subscriber', ['push' => 1, 'next_try' => DBA::NULL_DATETIME], $condition);
@ -52,7 +52,7 @@ class PushSubscriber
* @return void
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function requeue(int $default_priority = PRIORITY_HIGH)
public static function requeue(int $default_priority = Worker::PRIORITY_HIGH)
{
// We'll push to each subscriber that has push > 0,
// i.e. there has been an update (set in notifier.php).
@ -61,7 +61,7 @@ class PushSubscriber
while ($subscriber = DBA::fetch($subscribers)) {
// We always handle retries with low priority
if ($subscriber['push'] > 1) {
$priority = PRIORITY_LOW;
$priority = Worker::PRIORITY_LOW;
} else {
$priority = $default_priority;
}

View file

@ -23,6 +23,7 @@ namespace Friendica\Model;
use Friendica\Content\Pager;
use Friendica\Database\DBA;
use Friendica\Network\HTTPException;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Strings;
@ -113,21 +114,27 @@ class Register
}
/**
* Creates a register record for approval and returns the success of the database insert
* Creates a register record for approval
* Checks for the existence of the provided user id
*
* @param integer $uid The ID of the user needing approval
* @param string $language The registration language
* @param string $note An additional message from the user
* @return boolean
* @throws \Exception
* @param integer $uid The ID of the user needing approval
* @param string $language The registration language
* @param string $note An additional message from the user
* @return void
* @throws \OutOfBoundsException
* @throws HTTPException\InternalServerErrorException
* @throws HTTPException\NotFoundException
*/
public static function createForApproval(int $uid, string $language, string $note = ''): bool
public static function createForApproval(int $uid, string $language, string $note = ''): void
{
$hash = Strings::getRandomHex();
if (!$uid) {
throw new \OutOfBoundsException("User ID can't be empty");
}
if (!User::exists($uid)) {
return false;
throw new HTTPException\NotFoundException("User ID doesn't exist");
}
$fields = [
@ -139,7 +146,9 @@ class Register
'note' => $note
];
return DBA::insert('register', $fields);
if (!DBA::insert('register', $fields)) {
throw new HTTPException\InternalServerErrorException('Unable to insert a `register` record');
}
}
/**

View file

@ -141,7 +141,7 @@ class Subscription
{
$type = NotificationFactory::getType($notification);
if (DI::notify()->NotifyOnDesktop($notification, $type)) {
if (DI::notify()->shouldShowOnDesktop($notification, $type)) {
DI::notify()->createFromNotification($notification);
}
@ -152,7 +152,7 @@ class Subscription
$subscriptions = DBA::select('subscription', [], ['uid' => $notification->uid, $type => true]);
while ($subscription = DBA::fetch($subscriptions)) {
Logger::info('Push notification', ['id' => $subscription['id'], 'uid' => $subscription['uid'], 'type' => $type]);
Worker::add(PRIORITY_HIGH, 'PushSubscription', $subscription['id'], $notification->id);
Worker::add(Worker::PRIORITY_HIGH, 'PushSubscription', $subscription['id'], $notification->id);
}
DBA::close($subscriptions);
}

View file

@ -257,17 +257,16 @@ class Tag
* @param string $hash
* @param string $name
* @param string $url
* @param boolean $probing Whether probing is active
* @return void
*/
public static function storeByHash(int $uriId, string $hash, string $name, string $url = '', bool $probing = true)
public static function storeByHash(int $uriId, string $hash, string $name, string $url = '')
{
$type = self::getTypeForHash($hash);
if ($type == self::UNKNOWN) {
return;
}
self::store($uriId, $type, $name, $url, $probing);
self::store($uriId, $type, $name, $url);
}
/**
@ -297,34 +296,39 @@ class Tag
* @param integer $uriId URI-Id
* @param string $body Body of the post
* @param string $tags Accepted tags
* @param boolean $probing Perform a probing for contacts, adding them if needed
* @return void
*/
public static function storeFromBody(int $uriId, string $body, string $tags = null, bool $probing = true)
public static function storeFromBody(int $uriId, string $body, string $tags = null)
{
Logger::info('Check for tags', ['uri-id' => $uriId, 'hash' => $tags, 'callstack' => System::callstack()]);
$item = ['uri-id' => $uriId, 'body' => $body, 'quote-uri-id' => null];
self::storeFromArray($item, $tags);
}
/**
* Store tags and mentions from the item array
*
* @param array $item Item array
* @param string $tags Accepted tags
* @return void
*/
public static function storeFromArray(array $item, string $tags = null)
{
Logger::info('Check for tags', ['uri-id' => $item['uri-id'], 'hash' => $tags, 'callstack' => System::callstack()]);
if (is_null($tags)) {
$tags = self::TAG_CHARACTER[self::HASHTAG] . self::TAG_CHARACTER[self::MENTION] . self::TAG_CHARACTER[self::EXCLUSIVE_MENTION];
}
// Only remove the shared data from "real" reshares
$shared = BBCode::fetchShareAttributes($body);
if (!empty($shared['guid'])) {
if (preg_match("/\s*\[share .*?\](.*?)\[\/share\]\s*/ism", $body, $matches)) {
$share_body = $matches[1];
}
$body = preg_replace("/\s*\[share .*?\].*?\[\/share\]\s*/ism", '', $body);
foreach (self::getFromBody($item['body'], $tags) as $tag) {
self::storeByHash($item['uri-id'], $tag[1], $tag[3], $tag[2]);
}
foreach (self::getFromBody($body, $tags) as $tag) {
self::storeByHash($uriId, $tag[1], $tag[3], $tag[2], $probing);
}
$shared = DI::contentItem()->getSharedPost($item, ['uri-id']);
// Search for hashtags in the shared body (but only if hashtags are wanted)
if (!empty($share_body) && (strpos($tags, self::TAG_CHARACTER[self::HASHTAG]) !== false)) {
foreach (self::getFromBody($share_body, self::TAG_CHARACTER[self::HASHTAG]) as $tag) {
self::storeByHash($uriId, $tag[1], $tag[3], $tag[2], $probing);
if (!empty($shared) && (strpos($tags, self::TAG_CHARACTER[self::HASHTAG]) !== false)) {
foreach (self::getByURIId($shared['post']['uri-id'], [self::HASHTAG]) as $tag) {
self::store($item['uri-id'], $tag['type'], $tag['name'], $tag['url']);
}
}
}

View file

@ -158,6 +158,7 @@ class User
$system['publish'] = false;
$system['net-publish'] = false;
$system['hide-friends'] = true;
$system['hidewall'] = true;
$system['prv_keywords'] = '';
$system['pub_keywords'] = '';
$system['address'] = '';
@ -265,7 +266,7 @@ class User
// List of possible actor names
$possible_accounts = ['friendica', 'actor', 'system', 'internal'];
foreach ($possible_accounts as $name) {
if (!DBA::exists('user', ['nickname' => $name, 'account_removed' => false, 'expire' => false]) &&
if (!DBA::exists('user', ['nickname' => $name, 'account_removed' => false, 'account_expired' => false]) &&
!DBA::exists('userd', ['username' => $name])) {
DI::config()->set('system', 'actor_name', $name);
return $name;
@ -381,17 +382,15 @@ class User
*
* @param array $fields
* @return array user
* @throws Exception
*/
public static function getFirstAdmin(array $fields = []) : array
{
if (!empty(DI::config()->get('config', 'admin_nickname'))) {
return self::getByNickname(DI::config()->get('config', 'admin_nickname'), $fields);
} elseif (!empty(DI::config()->get('config', 'admin_email'))) {
$adminList = explode(',', str_replace(' ', '', DI::config()->get('config', 'admin_email')));
return self::getByEmail($adminList[0], $fields);
} else {
return [];
}
return self::getAdminList()[0] ?? [];
}
/**
@ -667,6 +666,28 @@ class User
return $user;
}
/**
* Update the day of the last activity of the given user
*
* @param integer $uid
* @return void
*/
public static function updateLastActivity(int $uid)
{
$user = User::getById($uid, ['last-activity']);
if (empty($user)) {
return;
}
$current_day = DateTimeFormat::utcNow('Y-m-d');
if ($user['last-activity'] != $current_day) {
User::update(['last-activity' => $current_day], $uid);
// Set the last actitivy for all identities of the user
DBA::update('user', ['last-activity' => $current_day], ['parent-uid' => $uid, 'account_removed' => false]);
}
}
/**
* Generates a human-readable random password
*
@ -994,7 +1015,7 @@ class User
try {
$authurl = $openid->authUrl();
} catch (Exception $e) {
throw new Exception(DI::l10n()->t('We encountered a problem while logging in with the OpenID you provided. Please check the correct spelling of the ID.') . EOL . EOL . DI::l10n()->t('The error message was:') . $e->getMessage(), 0, $e);
throw new Exception(DI::l10n()->t('We encountered a problem while logging in with the OpenID you provided. Please check the correct spelling of the ID.') . '<br />' . DI::l10n()->t('The error message was:') . $e->getMessage(), 0, $e);
}
System::externalRedirect($authurl);
// NOTREACHED
@ -1054,11 +1075,8 @@ class User
// Disallow somebody creating an account using openid that uses the admin email address,
// since openid bypasses email verification. We'll allow it if there is not yet an admin account.
if (DI::config()->get('config', 'admin_email') && strlen($openid_url)) {
$adminlist = explode(',', str_replace(' ', '', strtolower(DI::config()->get('config', 'admin_email'))));
if (in_array(strtolower($email), $adminlist)) {
throw new Exception(DI::l10n()->t('Cannot use that email.'));
}
if (strlen($openid_url) && in_array(strtolower($email), self::getAdminEmailList())) {
throw new Exception(DI::l10n()->t('Cannot use that email.'));
}
$nickname = $data['nickname'] = strtolower($nickname);
@ -1317,7 +1335,7 @@ class User
if (DBA::isResult($profile) && $profile['net-publish'] && Search::getGlobalDirectory()) {
$url = DI::baseUrl() . '/profile/' . $user['nickname'];
Worker::add(PRIORITY_LOW, "Directory", $url);
Worker::add(Worker::PRIORITY_LOW, "Directory", $url);
}
$l10n = DI::l10n()->withLang($register['language']);
@ -1418,7 +1436,7 @@ class User
If you are new and do not know anybody here, they may help
you to make some new and interesting friends.
If you ever want to delete your account, you can do so at %1$s/removeme
If you ever want to delete your account, you can do so at %1$s/settings/removeme
Thank you and welcome to %4$s.'));
@ -1522,7 +1540,7 @@ class User
If you are new and do not know anybody here, they may help
you to make some new and interesting friends.
If you ever want to delete your account, you can do so at %3$s/removeme
If you ever want to delete your account, you can do so at %3$s/settings/removeme
Thank you and welcome to %2$s.',
$user['nickname'],
@ -1567,14 +1585,14 @@ class User
// The user and related data will be deleted in Friendica\Worker\ExpireAndRemoveUsers
DBA::update('user', ['account_removed' => true, 'account_expires_on' => DateTimeFormat::utc('now + 7 day')], ['uid' => $uid]);
Worker::add(PRIORITY_HIGH, 'Notifier', Delivery::REMOVAL, $uid);
Worker::add(Worker::PRIORITY_HIGH, 'Notifier', Delivery::REMOVAL, $uid);
// Send an update to the directory
$self = DBA::selectFirst('contact', ['url'], ['uid' => $uid, 'self' => true]);
Worker::add(PRIORITY_LOW, 'Directory', $self['url']);
Worker::add(Worker::PRIORITY_LOW, 'Directory', $self['url']);
// Remove the user relevant data
Worker::add(PRIORITY_NEGLIGIBLE, 'RemoveUser', $uid);
Worker::add(Worker::PRIORITY_NEGLIGIBLE, 'RemoveUser', $uid);
return true;
}
@ -1714,8 +1732,8 @@ class User
'active_users_weekly' => 0,
];
$userStmt = DBA::select('owner-view', ['uid', 'login_date', 'last-item'],
["`verified` AND `login_date` > ? AND NOT `blocked`
$userStmt = DBA::select('owner-view', ['uid', 'last-activity', 'last-item'],
["`verified` AND `last-activity` > ? AND NOT `blocked`
AND NOT `account_removed` AND NOT `account_expired`",
DBA::NULL_DATETIME]);
if (!DBA::isResult($userStmt)) {
@ -1729,17 +1747,17 @@ class User
while ($user = DBA::fetch($userStmt)) {
$statistics['total_users']++;
if ((strtotime($user['login_date']) > $halfyear) || (strtotime($user['last-item']) > $halfyear)
if ((strtotime($user['last-activity']) > $halfyear) || (strtotime($user['last-item']) > $halfyear)
) {
$statistics['active_users_halfyear']++;
}
if ((strtotime($user['login_date']) > $month) || (strtotime($user['last-item']) > $month)
if ((strtotime($user['last-activity']) > $month) || (strtotime($user['last-item']) > $month)
) {
$statistics['active_users_monthly']++;
}
if ((strtotime($user['login_date']) > $week) || (strtotime($user['last-item']) > $week)
if ((strtotime($user['last-activity']) > $week) || (strtotime($user['last-item']) > $week)
) {
$statistics['active_users_weekly']++;
}
@ -1783,4 +1801,64 @@ class User
return DBA::selectToArray('owner-view', [], $condition, $param);
}
/**
* Returns a list of lowercase admin email addresses from the comma-separated list in the config
*
* @return array
*/
public static function getAdminEmailList(): array
{
$adminEmails = strtolower(str_replace(' ', '', DI::config()->get('config', 'admin_email')));
if (!$adminEmails) {
return [];
}
return explode(',', $adminEmails);
}
/**
* Returns the complete list of admin user accounts
*
* @param array $fields
* @return array
* @throws Exception
*/
public static function getAdminList(array $fields = []): array
{
$condition = [
'email' => self::getAdminEmailList(),
'parent-uid' => 0,
'blocked' => 0,
'verified' => true,
'account_removed' => false,
'account_expired' => false,
];
return DBA::selectToArray('user', $fields, $condition, ['order' => ['uid']]);
}
/**
* Return a list of admin user accounts where each unique email address appears only once.
*
* This method is meant for admin notifications that do not need to be sent multiple times to the same email address.
*
* @param array $fields
* @return array
* @throws Exception
*/
public static function getAdminListForEmailing(array $fields = []): array
{
return array_filter(self::getAdminList($fields), function ($user) {
static $emails = [];
if (in_array($user['email'], $emails)) {
return false;
}
$emails[] = $user['email'];
return true;
});
}
}