X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FModel%2FItem.php;h=3f3de8f8f1f405e11a497508c3da3b3232dd567e;hb=e93fba51362a8cc212cb42542fd013ffd28b1164;hp=b5c68d9ab7cbfc87a57bbf1c09be3eb130d7ae23;hpb=052659517b9459915080f39d827aa91f1c9ff841;p=friendica.git diff --git a/src/Model/Item.php b/src/Model/Item.php index b5c68d9ab7..3f3de8f8f1 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -6,7 +6,6 @@ namespace Friendica\Model; -use Friendica\BaseObject; use Friendica\Content\Text\BBCode; use Friendica\Content\Text\HTML; use Friendica\Core\Config; @@ -17,9 +16,12 @@ use Friendica\Core\Logger; use Friendica\Core\PConfig; use Friendica\Core\Protocol; use Friendica\Core\Renderer; +use Friendica\Core\Session; use Friendica\Core\System; use Friendica\Core\Worker; use Friendica\Database\DBA; +use Friendica\DI; +use Friendica\Protocol\Activity; use Friendica\Protocol\ActivityPub; use Friendica\Protocol\Diaspora; use Friendica\Protocol\OStatus; @@ -32,7 +34,7 @@ use Friendica\Util\XML; use Friendica\Worker\Delivery; use Text_LanguageDetect; -class Item extends BaseObject +class Item { // Posting types, inspired by https://www.w3.org/TR/activitystreams-vocabulary/#object-types const PT_ARTICLE = 0; @@ -55,7 +57,7 @@ class Item extends BaseObject 'author-id', 'author-link', 'author-name', 'author-avatar', 'author-network', 'owner-id', 'owner-link', 'owner-name', 'owner-avatar', 'owner-network', 'contact-id', 'contact-uid', 'contact-link', 'contact-name', 'contact-avatar', - 'writable', 'self', 'cid', 'alias', + 'writable', 'self', 'cid', 'alias', 'pinned', 'event-id', 'event-created', 'event-edited', 'event-start', 'event-finish', 'event-summary', 'event-desc', 'event-location', 'event-type', 'event-nofinish', 'event-adjust', 'event-ignore', 'event-id', @@ -94,7 +96,11 @@ class Item extends BaseObject // Never reorder or remove entries from this list. Just add new ones at the end, if needed. // The item-activity table only stores the index and needs this array to know the matching activity. - const ACTIVITIES = [ACTIVITY_LIKE, ACTIVITY_DISLIKE, ACTIVITY_ATTEND, ACTIVITY_ATTENDNO, ACTIVITY_ATTENDMAYBE, ACTIVITY_FOLLOW, ACTIVITY2_ANNOUNCE]; + const ACTIVITIES = [ + Activity::LIKE, Activity::DISLIKE, + Activity::ATTEND, Activity::ATTENDNO, Activity::ATTENDMAYBE, + Activity::FOLLOW, + Activity::ANNOUNCE]; private static $legacy_mode = null; @@ -107,6 +113,80 @@ class Item extends BaseObject return self::$legacy_mode; } + /** + * Set the pinned state of an item + * + * @param integer $iid Item ID + * @param integer $uid User ID + * @param boolean $pinned Pinned state + */ + public static function setPinned(int $iid, int $uid, bool $pinned) + { + DBA::update('user-item', ['pinned' => $pinned], ['iid' => $iid, 'uid' => $uid], true); + } + + /** + * Get the pinned state + * + * @param integer $iid Item ID + * @param integer $uid User ID + * + * @return boolean pinned state + */ + public static function getPinned(int $iid, int $uid) + { + $useritem = DBA::selectFirst('user-item', ['pinned'], ['iid' => $iid, 'uid' => $uid]); + if (!DBA::isResult($useritem)) { + return false; + } + return (bool)$useritem['pinned']; + } + + /** + * @brief Select pinned rows from the item table for a given user + * + * @param integer $uid User ID + * @param array $selected Array of selected fields, empty for all + * @param array $condition Array of fields for condition + * @param array $params Array of several parameters + * + * @return boolean|object + * @throws \Exception + */ + public static function selectPinned(int $uid, array $selected = [], array $condition = [], $params = []) + { + $useritems = DBA::select('user-item', ['iid'], ['uid' => $uid, 'pinned' => true]); + if (!DBA::isResult($useritems)) { + return $useritems; + } + + $pinned = []; + while ($useritem = self::fetch($useritems)) { + $pinned[] = $useritem['iid']; + } + DBA::close($useritems); + + if (empty($pinned)) { + return []; + } + + if (empty($condition) || !is_array($condition)) { + $condition = ['iid' => $pinned]; + } else { + reset($condition); + $first_key = key($condition); + if (!is_int($first_key)) { + $condition['iid'] = $pinned; + } else { + $values_string = substr(str_repeat("?, ", count($pinned)), 0, -2); + $condition[0] = '(' . $condition[0] . ") AND `iid` IN (" . $values_string . ")"; + $condition = array_merge($condition, $pinned); + } + } + + return self::selectThreadForUser($uid, $selected, $condition, $params); + } + /** * @brief returns an activity index from an activity string * @@ -207,21 +287,19 @@ class Item extends BaseObject $row['object'] = ''; } if (array_key_exists('object-type', $row)) { - $row['object-type'] = ACTIVITY_OBJ_NOTE; + $row['object-type'] = Activity\ObjectType::NOTE; } - } elseif (array_key_exists('verb', $row) && in_array($row['verb'], ['', ACTIVITY_POST, ACTIVITY_SHARE])) { - // Posts don't have an object or target - but having tags or files. + } elseif (array_key_exists('verb', $row) && in_array($row['verb'], ['', Activity::POST, Activity::SHARE])) { + // Posts don't have a target - but having tags or files. // We safe some performance by building tag and file strings only here. - // We remove object and target since they aren't used for this type. - if (array_key_exists('object', $row)) { - $row['object'] = ''; - } + // We remove the target since they aren't used for this type. + // In mail posts we do store some mail header data in the object. if (array_key_exists('target', $row)) { $row['target'] = ''; } } - if (!array_key_exists('verb', $row) || in_array($row['verb'], ['', ACTIVITY_POST, ACTIVITY_SHARE])) { + if (!array_key_exists('verb', $row) || in_array($row['verb'], ['', Activity::POST, Activity::SHARE])) { // Build the tag string out of the term entries if (array_key_exists('tag', $row) && empty($row['tag'])) { $row['tag'] = Term::tagTextFromItemId($row['internal-iid']); @@ -578,7 +656,7 @@ class Item extends BaseObject 'iaid' => 'internal-iaid']; if ($usermode) { - $fields['user-item'] = ['ignored' => 'internal-user-ignored']; + $fields['user-item'] = ['pinned', 'ignored' => 'internal-user-ignored']; } $fields['item-activity'] = ['activity', 'activity' => 'internal-activity']; @@ -1004,6 +1082,9 @@ class Item extends BaseObject // "Deleting" global items just means hiding them if ($item['uid'] == 0) { DBA::update('user-item', ['hidden' => true], ['iid' => $item['id'], 'uid' => $uid], true); + + // Delete notifications + DBA::delete('notify', ['iid' => $item['id'], 'uid' => $uid]); } elseif ($item['uid'] == $uid) { self::deleteById($item['id'], PRIORITY_HIGH); } else { @@ -1094,6 +1175,9 @@ class Item extends BaseObject // Delete tags that had been attached to other items self::deleteTagsFromItem($item); + // Delete notifications + DBA::delete('notify', ['iid' => $item['id'], 'uid' => $item['uid']]); + // Set the item to "deleted" $item_fields = ['deleted' => true, 'edited' => DateTimeFormat::utcNow(), 'changed' => DateTimeFormat::utcNow()]; DBA::update('item', $item_fields, ['id' => $item['id']]); @@ -1150,14 +1234,14 @@ class Item extends BaseObject private static function deleteTagsFromItem($item) { - if (($item["verb"] != ACTIVITY_TAG) || ($item["object-type"] != ACTIVITY_OBJ_TAGTERM)) { + if (($item["verb"] != Activity::TAG) || ($item["object-type"] != Activity\ObjectType::TAGTERM)) { return; } $xo = XML::parseString($item["object"], false); $xt = XML::parseString($item["target"], false); - if ($xt->type != ACTIVITY_OBJ_NOTE) { + if ($xt->type != Activity\ObjectType::NOTE) { return; } @@ -1312,11 +1396,11 @@ class Item extends BaseObject $priority = $notify; } } else { - $item['network'] = trim(defaults($item, 'network', Protocol::PHANTOM)); + $item['network'] = trim(($item['network'] ?? '') ?: Protocol::PHANTOM); } $item['guid'] = self::guid($item, $notify); - $item['uri'] = Strings::escapeTags(trim(defaults($item, 'uri', self::newURI($item['uid'], $item['guid'])))); + $item['uri'] = Strings::escapeTags(trim(($item['uri'] ?? '') ?: self::newURI($item['uid'], $item['guid']))); // Store URI data $item['uri-id'] = ItemURI::insert(['uri' => $item['uri'], 'guid' => $item['guid']]); @@ -1356,13 +1440,15 @@ class Item extends BaseObject $item['parent-uri'] = $item['thr-parent']; } + $activity = DI::activity(); + if (isset($item['gravity'])) { $item['gravity'] = intval($item['gravity']); } elseif ($item['parent-uri'] === $item['uri']) { $item['gravity'] = GRAVITY_PARENT; - } elseif (activity_match($item['verb'], ACTIVITY_POST)) { + } elseif ($activity->match($item['verb'], Activity::POST)) { $item['gravity'] = GRAVITY_COMMENT; - } elseif (activity_match($item['verb'], ACTIVITY_FOLLOW)) { + } elseif ($activity->match($item['verb'], Activity::FOLLOW)) { $item['gravity'] = GRAVITY_ACTIVITY; } else { $item['gravity'] = GRAVITY_UNKNOWN; // Should not happen @@ -1418,47 +1504,47 @@ class Item extends BaseObject } } - $item['wall'] = intval(defaults($item, 'wall', 0)); - $item['extid'] = trim(defaults($item, 'extid', '')); - $item['author-name'] = trim(defaults($item, 'author-name', '')); - $item['author-link'] = trim(defaults($item, 'author-link', '')); - $item['author-avatar'] = trim(defaults($item, 'author-avatar', '')); - $item['owner-name'] = trim(defaults($item, 'owner-name', '')); - $item['owner-link'] = trim(defaults($item, 'owner-link', '')); - $item['owner-avatar'] = trim(defaults($item, 'owner-avatar', '')); + $item['wall'] = intval($item['wall'] ?? 0); + $item['extid'] = trim($item['extid'] ?? ''); + $item['author-name'] = trim($item['author-name'] ?? ''); + $item['author-link'] = trim($item['author-link'] ?? ''); + $item['author-avatar'] = trim($item['author-avatar'] ?? ''); + $item['owner-name'] = trim($item['owner-name'] ?? ''); + $item['owner-link'] = trim($item['owner-link'] ?? ''); + $item['owner-avatar'] = trim($item['owner-avatar'] ?? ''); $item['received'] = (isset($item['received']) ? DateTimeFormat::utc($item['received']) : DateTimeFormat::utcNow()); $item['created'] = (isset($item['created']) ? DateTimeFormat::utc($item['created']) : $item['received']); $item['edited'] = (isset($item['edited']) ? DateTimeFormat::utc($item['edited']) : $item['created']); $item['changed'] = (isset($item['changed']) ? DateTimeFormat::utc($item['changed']) : $item['created']); $item['commented'] = (isset($item['commented']) ? DateTimeFormat::utc($item['commented']) : $item['created']); - $item['title'] = trim(defaults($item, 'title', '')); - $item['location'] = trim(defaults($item, 'location', '')); - $item['coord'] = trim(defaults($item, 'coord', '')); + $item['title'] = trim($item['title'] ?? ''); + $item['location'] = trim($item['location'] ?? ''); + $item['coord'] = trim($item['coord'] ?? ''); $item['visible'] = (isset($item['visible']) ? intval($item['visible']) : 1); $item['deleted'] = 0; - $item['parent-uri'] = trim(defaults($item, 'parent-uri', $item['uri'])); - $item['post-type'] = defaults($item, 'post-type', self::PT_ARTICLE); - $item['verb'] = trim(defaults($item, 'verb', '')); - $item['object-type'] = trim(defaults($item, 'object-type', '')); - $item['object'] = trim(defaults($item, 'object', '')); - $item['target-type'] = trim(defaults($item, 'target-type', '')); - $item['target'] = trim(defaults($item, 'target', '')); - $item['plink'] = trim(defaults($item, 'plink', '')); - $item['allow_cid'] = trim(defaults($item, 'allow_cid', '')); - $item['allow_gid'] = trim(defaults($item, 'allow_gid', '')); - $item['deny_cid'] = trim(defaults($item, 'deny_cid', '')); - $item['deny_gid'] = trim(defaults($item, 'deny_gid', '')); - $item['private'] = intval(defaults($item, 'private', 0)); - $item['body'] = trim(defaults($item, 'body', '')); - $item['tag'] = trim(defaults($item, 'tag', '')); - $item['attach'] = trim(defaults($item, 'attach', '')); - $item['app'] = trim(defaults($item, 'app', '')); - $item['origin'] = intval(defaults($item, 'origin', 0)); - $item['postopts'] = trim(defaults($item, 'postopts', '')); - $item['resource-id'] = trim(defaults($item, 'resource-id', '')); - $item['event-id'] = intval(defaults($item, 'event-id', 0)); - $item['inform'] = trim(defaults($item, 'inform', '')); - $item['file'] = trim(defaults($item, 'file', '')); + $item['parent-uri'] = trim(($item['parent-uri'] ?? '') ?: $item['uri']); + $item['post-type'] = ($item['post-type'] ?? '') ?: self::PT_ARTICLE; + $item['verb'] = trim($item['verb'] ?? ''); + $item['object-type'] = trim($item['object-type'] ?? ''); + $item['object'] = trim($item['object'] ?? ''); + $item['target-type'] = trim($item['target-type'] ?? ''); + $item['target'] = trim($item['target'] ?? ''); + $item['plink'] = trim($item['plink'] ?? ''); + $item['allow_cid'] = trim($item['allow_cid'] ?? ''); + $item['allow_gid'] = trim($item['allow_gid'] ?? ''); + $item['deny_cid'] = trim($item['deny_cid'] ?? ''); + $item['deny_gid'] = trim($item['deny_gid'] ?? ''); + $item['private'] = intval($item['private'] ?? 0); + $item['body'] = trim($item['body'] ?? ''); + $item['tag'] = trim($item['tag'] ?? ''); + $item['attach'] = trim($item['attach'] ?? ''); + $item['app'] = trim($item['app'] ?? ''); + $item['origin'] = intval($item['origin'] ?? 0); + $item['postopts'] = trim($item['postopts'] ?? ''); + $item['resource-id'] = trim($item['resource-id'] ?? ''); + $item['event-id'] = intval($item['event-id'] ?? 0); + $item['inform'] = trim($item['inform'] ?? ''); + $item['file'] = trim($item['file'] ?? ''); // When there is no content then we don't post it if ($item['body'].$item['title'] == '') { @@ -1478,12 +1564,12 @@ class Item extends BaseObject $item['edited'] = DateTimeFormat::utcNow(); } - $item['plink'] = defaults($item, 'plink', System::baseUrl() . '/display/' . urlencode($item['guid'])); + $item['plink'] = ($item['plink'] ?? '') ?: System::baseUrl() . '/display/' . urlencode($item['guid']); $default = ['url' => $item['author-link'], 'name' => $item['author-name'], 'photo' => $item['author-avatar'], 'network' => $item['network']]; - $item['author-id'] = defaults($item, 'author-id', Contact::getIdForURL($item['author-link'], 0, false, $default)); + $item['author-id'] = ($item['author-id'] ?? 0) ?: Contact::getIdForURL($item['author-link'], 0, false, $default); if (Contact::isBlocked($item['author-id'])) { Logger::notice('Author is blocked node-wide', ['author-link' => $item['author-link'], 'item-uri' => $item['uri']]); @@ -1503,7 +1589,7 @@ class Item extends BaseObject $default = ['url' => $item['owner-link'], 'name' => $item['owner-name'], 'photo' => $item['owner-avatar'], 'network' => $item['network']]; - $item['owner-id'] = defaults($item, 'owner-id', Contact::getIdForURL($item['owner-link'], 0, false, $default)); + $item['owner-id'] = ($item['owner-id'] ?? 0) ?: Contact::getIdForURL($item['owner-link'], 0, false, $default); if (Contact::isBlocked($item['owner-id'])) { Logger::notice('Owner is blocked node-wide', ['owner-link' => $item['owner-link'], 'item-uri' => $item['uri']]); @@ -1558,14 +1644,14 @@ class Item extends BaseObject return 0; } - if ($item['verb'] == ACTIVITY_FOLLOW) { + if ($item['verb'] == Activity::FOLLOW) { if (!$item['origin'] && ($item['author-id'] == Contact::getPublicIdByUserId($uid))) { // Our own follow request can be relayed to us. We don't store it to avoid notification chaos. Logger::log("Follow: Don't store not origin follow request from us for " . $item['parent-uri'], Logger::DEBUG); return 0; } - $condition = ['verb' => ACTIVITY_FOLLOW, 'uid' => $item['uid'], + $condition = ['verb' => Activity::FOLLOW, 'uid' => $item['uid'], 'parent-uri' => $item['parent-uri'], 'author-id' => $item['author-id']]; if (self::exists($condition)) { // It happens that we receive multiple follow requests by the same author - we only store one. @@ -1672,7 +1758,7 @@ class Item extends BaseObject } } - if (stristr($item['verb'], ACTIVITY_POKE)) { + if (stristr($item['verb'], Activity::POKE)) { $notify_type = Delivery::POKE; } @@ -2425,7 +2511,7 @@ class Item extends BaseObject $guid = System::createUUID(); } - return self::getApp()->getBaseURL() . '/objects/' . $guid; + return DI::app()->getBaseURL() . '/objects/' . $guid; } /** @@ -2452,7 +2538,7 @@ class Item extends BaseObject Contact::unmarkForArchival($contact); } - $update = (!$arr['private'] && ((defaults($arr, 'author-link', '') === defaults($arr, 'owner-link', '')) || ($arr["parent-uri"] === $arr["uri"]))); + $update = (!$arr['private'] && ((($arr['author-link'] ?? '') === ($arr['owner-link'] ?? '')) || ($arr["parent-uri"] === $arr["uri"]))); // Is it a forum? Then we don't care about the rules from above if (!$update && in_array($arr["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN]) && ($arr["parent-uri"] === $arr["uri"])) { @@ -2533,7 +2619,7 @@ class Item extends BaseObject "#$2", $item["body"]); foreach ($tags as $tag) { - if ((strpos($tag, '#') !== 0) || strpos($tag, '[url=') || $tag[1] == '#') { + if ((strpos($tag, '#') !== 0) || strpos($tag, '[url=') || strlen($tag) < 2 || $tag[1] == '#') { continue; } @@ -2685,7 +2771,7 @@ class Item extends BaseObject } // Only forward posts - if ($datarray["verb"] != ACTIVITY_POST) { + if ($datarray["verb"] != Activity::POST) { Logger::log('No post', Logger::DEBUG); return false; } @@ -2891,10 +2977,12 @@ class Item extends BaseObject */ public static function enumeratePermissions(array $obj, bool $check_dead = false) { - $allow_people = expand_acl($obj['allow_cid']); - $allow_groups = Group::expand($obj['uid'], expand_acl($obj['allow_gid']), $check_dead); - $deny_people = expand_acl($obj['deny_cid']); - $deny_groups = Group::expand($obj['uid'], expand_acl($obj['deny_gid']), $check_dead); + $aclFormater = DI::aclFormatter(); + + $allow_people = $aclFormater->expand($obj['allow_cid']); + $allow_groups = Group::expand($obj['uid'], $aclFormater->expand($obj['allow_gid']), $check_dead); + $deny_people = $aclFormater->expand($obj['deny_cid']); + $deny_groups = Group::expand($obj['uid'], $aclFormater->expand($obj['deny_gid']), $check_dead); $recipients = array_unique(array_merge($allow_people, $allow_groups)); $deny = array_unique(array_merge($deny_people, $deny_groups)); $recipients = array_diff($recipients, $deny); @@ -3028,30 +3116,30 @@ class Item extends BaseObject */ public static function performLike($item_id, $verb) { - if (!local_user() && !remote_user()) { + if (!Session::isAuthenticated()) { return false; } switch ($verb) { case 'like': case 'unlike': - $activity = ACTIVITY_LIKE; + $activity = Activity::LIKE; break; case 'dislike': case 'undislike': - $activity = ACTIVITY_DISLIKE; + $activity = Activity::DISLIKE; break; case 'attendyes': case 'unattendyes': - $activity = ACTIVITY_ATTEND; + $activity = Activity::ATTEND; break; case 'attendno': case 'unattendno': - $activity = ACTIVITY_ATTENDNO; + $activity = Activity::ATTENDNO; break; case 'attendmaybe': case 'unattendmaybe': - $activity = ACTIVITY_ATTENDMAYBE; + $activity = Activity::ATTENDMAYBE; break; default: Logger::log('like: unknown verb ' . $verb . ' for item ' . $item_id); @@ -3059,7 +3147,7 @@ class Item extends BaseObject } // Enable activity toggling instead of on/off - $event_verb_flag = $activity === ACTIVITY_ATTEND || $activity === ACTIVITY_ATTENDNO || $activity === ACTIVITY_ATTENDMAYBE; + $event_verb_flag = $activity === Activity::ATTEND || $activity === Activity::ATTENDNO || $activity === Activity::ATTENDMAYBE; Logger::log('like: verb ' . $verb . ' item ' . $item_id); @@ -3113,7 +3201,7 @@ class Item extends BaseObject // event participation are essentially radio toggles. If you make a subsequent choice, // we need to eradicate your first choice. if ($event_verb_flag) { - $verbs = [ACTIVITY_ATTEND, ACTIVITY_ATTENDNO, ACTIVITY_ATTENDMAYBE]; + $verbs = [Activity::ATTEND, Activity::ATTENDNO, Activity::ATTENDMAYBE]; // Translate to the index based activity index $activities = []; @@ -3143,7 +3231,7 @@ class Item extends BaseObject return true; } - $objtype = $item['resource-id'] ? ACTIVITY_OBJ_IMAGE : ACTIVITY_OBJ_NOTE; + $objtype = $item['resource-id'] ? Activity\ObjectType::IMAGE : Activity\ObjectType::NOTE; $new_item = [ 'guid' => System::createUUID(), @@ -3260,10 +3348,10 @@ class Item extends BaseObject } } - public static function getPermissionsSQLByUserId($owner_id, $remote_verified = false, $groups = null, $remote_cid = null) + public static function getPermissionsSQLByUserId($owner_id) { $local_user = local_user(); - $remote_user = remote_user(); + $remote_user = Session::getRemoteContactID($owner_id); /* * Construct permissions @@ -3283,7 +3371,7 @@ class Item extends BaseObject * If pre-verified, the caller is expected to have already * done this and passed the groups into this function. */ - $set = PermissionSet::get($owner_id, $remote_cid, $groups); + $set = PermissionSet::get($owner_id, $remote_user); if (!empty($set)) { $sql_set = " OR (`item`.`private` IN (1,2) AND `item`.`wall` AND `item`.`psid` IN (" . implode(',', $set) . "))"; @@ -3309,7 +3397,7 @@ class Item extends BaseObject return L10n::t('event'); } elseif (!empty($item['resource-id'])) { return L10n::t('photo'); - } elseif (!empty($item['verb']) && $item['verb'] !== ACTIVITY_POST) { + } elseif (!empty($item['verb']) && $item['verb'] !== Activity::POST) { return L10n::t('activity'); } elseif ($item['id'] != $item['parent']) { return L10n::t('comment'); @@ -3333,18 +3421,17 @@ class Item extends BaseObject { $body = $item["body"]; - $rendered_hash = defaults($item, 'rendered-hash', ''); - $rendered_html = defaults($item, 'rendered-html', ''); + $rendered_hash = $item['rendered-hash'] ?? ''; + $rendered_html = $item['rendered-html'] ?? ''; if ($rendered_hash == '' || $rendered_html == "" || $rendered_hash != hash("md5", $item["body"]) || Config::get("system", "ignore_cache") ) { - $a = self::getApp(); - redir_private_images($a, $item); + self::addRedirToImageTags($item); - $item["rendered-html"] = prepare_text($item["body"]); + $item["rendered-html"] = BBCode::convert($item["body"]); $item["rendered-hash"] = hash("md5", $item["body"]); $hook_data = ['item' => $item, 'rendered-html' => $item['rendered-html'], 'rendered-hash' => $item['rendered-hash']]; @@ -3377,6 +3464,31 @@ class Item extends BaseObject $item["body"] = $body; } + /** + * @brief Find any non-embedded images in private items and add redir links to them + * + * @param array &$item The field array of an item row + */ + private static function addRedirToImageTags(array &$item) + { + $app = DI::app(); + + $matches = []; + $cnt = preg_match_all('|\[img\](http[^\[]*?/photo/[a-fA-F0-9]+?(-[0-9]\.[\w]+?)?)\[\/img\]|', $item['body'], $matches, PREG_SET_ORDER); + if ($cnt) { + foreach ($matches as $mtch) { + if (strpos($mtch[1], '/redir') !== false) { + continue; + } + + if ((local_user() == $item['uid']) && ($item['private'] == 1) && ($item['contact-id'] != $app->contact['id']) && ($item['network'] == Protocol::DFRN)) { + $img_url = 'redir/' . $item['contact-id'] . '?url=' . urlencode($mtch[1]); + $item['body'] = str_replace($mtch[0], '[img]' . $img_url . '[/img]', $item['body']); + } + } + } + } + /** * @brief Given an item array, convert the body element from bbcode to html and add smilie icons. * If attach is true, also add icons for item attachments. @@ -3394,12 +3506,12 @@ class Item extends BaseObject */ public static function prepareBody(array &$item, $attach = false, $is_preview = false) { - $a = self::getApp(); + $a = DI::app(); Hook::callAll('prepare_body_init', $item); // In order to provide theme developers more possibilities, event items // are treated differently. - if ($item['object-type'] === ACTIVITY_OBJ_EVENT && isset($item['event-id'])) { + if ($item['object-type'] === Activity\ObjectType::EVENT && isset($item['event-id'])) { $ev = Event::getItemHTML($item); return $ev; } @@ -3427,7 +3539,7 @@ class Item extends BaseObject } // Update the cached values if there is no "zrl=..." on the links. - $update = (!local_user() && !remote_user() && ($item["uid"] == 0)); + $update = (!Session::isAuthenticated() && ($item["uid"] == 0)); // Or update it if the current viewer is the intented viewer. if (($item["uid"] == local_user()) && ($item["uid"] != 0)) { @@ -3490,7 +3602,7 @@ class Item extends BaseObject $filesubtype = 'unkn'; } - $title = Strings::escapeHtml(trim(defaults($mtch, 4, $mtch[1]))); + $title = Strings::escapeHtml(trim(($mtch[4] ?? '') ?: $mtch[1])); $title .= ' ' . $mtch[2] . ' ' . L10n::t('bytes'); $icon = '
'; @@ -3532,7 +3644,7 @@ class Item extends BaseObject */ public static function getPlink($item) { - $a = self::getApp(); + $a = DI::app(); if ($a->user['nickname'] != "") { $ret = [ @@ -3543,7 +3655,7 @@ class Item extends BaseObject ]; if (!empty($item['plink'])) { - $ret["href"] = $a->removeBaseURL($item['plink']); + $ret["href"] = DI::baseUrl()->remove($item['plink']); $ret["title"] = L10n::t('link to source'); } @@ -3650,4 +3762,83 @@ class Item extends BaseObject return 0; } + + /** + * Return share data from an item array (if the item is shared item) + * We are providing the complete Item array, because at some time in the future + * we hopefully will define these values not in the body anymore but in some item fields. + * This function is meant to replace all similar functions in the system. + * + * @param array $item + * + * @return array with share information + */ + public static function getShareArray($item) + { + if (!preg_match("/(.*?)\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", $item['body'], $matches)) { + return []; + } + + $attribute_string = $matches[2]; + $attributes = ['comment' => trim($matches[1]), 'shared' => trim($matches[3])]; + foreach (['author', 'profile', 'avatar', 'guid', 'posted', 'link'] as $field) { + if (preg_match("/$field=(['\"])(.+?)\\1/ism", $attribute_string, $matches)) { + $attributes[$field] = trim(html_entity_decode($matches[2] ?? '', ENT_QUOTES, 'UTF-8')); + } + } + return $attributes; + } + + /** + * Fetch item information for shared items from the original items and adds it. + * + * @param array $item + * + * @return array item array with data from the original item + */ + public static function addShareDataFromOriginal($item) + { + $shared = self::getShareArray($item); + if (empty($shared)) { + return $item; + } + + // Real reshares always have got a GUID. + if (empty($shared['guid'])) { + return $item; + } + + $uid = $item['uid'] ?? 0; + + // first try to fetch the item via the GUID. This will work for all reshares that had been created on this system + $shared_item = self::selectFirst(['title', 'body', 'attach'], ['guid' => $shared['guid'], 'uid' => [0, $uid]]); + if (!DBA::isResult($shared_item)) { + // Otherwhise try to find (and possibly fetch) the item via the link. This should work for Diaspora and ActivityPub posts + $id = self::fetchByLink($shared['link'], $uid); + if (empty($id)) { + Logger::info('Original item not found', ['url' => $shared['link'], 'callstack' => System::callstack()]); + return $item; + } + + $shared_item = self::selectFirst(['title', 'body', 'attach'], ['id' => $id]); + if (!DBA::isResult($shared_item)) { + return $item; + } + Logger::info('Got shared data from url', ['url' => $shared['link'], 'callstack' => System::callstack()]); + } else { + Logger::info('Got shared data from guid', ['guid' => $shared['guid'], 'callstack' => System::callstack()]); + } + + if (!empty($shared_item['title'])) { + $body = '[h3]' . $shared_item['title'] . "[/h3]\n" . $shared_item['body']; + unset($shared_item['title']); + } else { + $body = $shared_item['body']; + } + + $item['body'] = preg_replace("/\[share ([^\[\]]*)\].*\[\/share\]/ism", '[share $1]' . $body . '[/share]', $item['body']); + unset($shared_item['body']); + + return array_merge($item, $shared_item); + } }