diff --git a/boot.php b/boot.php index 0637ff3fea..a070e103ea 100644 --- a/boot.php +++ b/boot.php @@ -33,7 +33,6 @@ use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Contact; use Friendica\Model\Notify; -use Friendica\Model\Term; use Friendica\Util\BasePath; use Friendica\Util\DateTimeFormat; diff --git a/include/api.php b/include/api.php index 36b08f08f8..c04174313e 100644 --- a/include/api.php +++ b/include/api.php @@ -41,7 +41,6 @@ use Friendica\Model\Item; use Friendica\Model\Mail; use Friendica\Model\Notify; use Friendica\Model\Photo; -use Friendica\Model\Term; use Friendica\Model\User; use Friendica\Model\UserItem; use Friendica\Network\FKOAuth1; @@ -1539,31 +1538,24 @@ function api_search($type) $params = ['order' => ['id' => true], 'limit' => [$start, $count]]; if (preg_match('/^#(\w+)$/', $searchTerm, $matches) === 1 && isset($matches[1])) { $searchTerm = $matches[1]; - $condition = ["`oid` > ? - AND (`uid` = 0 OR (`uid` = ? AND NOT `global`)) - AND `otype` = ? AND `type` = ? AND `term` = ?", - $since_id, local_user(), Term::OBJECT_TYPE_POST, Term::HASHTAG, $searchTerm]; - if ($max_id > 0) { - $condition[0] .= ' AND `oid` <= ?'; - $condition[] = $max_id; + $condition = ["`iid` > ? AND `name` = ? AND (NOT `private` OR (`private` AND `uid` = ?))", $since_id, $searchTerm, local_user()]; + $tags = DBA::select('tag-search-view', ['uri-id'], $condition); + $uriids = []; + while ($tag = DBA::fetch($tags)) { + $uriids[] = $tag['uri-id']; } - $terms = DBA::select('term', ['oid'], $condition, []); - $itemIds = []; - while ($term = DBA::fetch($terms)) { - $itemIds[] = $term['oid']; - } - DBA::close($terms); + DBA::close($tags); - if (empty($itemIds)) { + if (empty($uriids)) { return api_format_data('statuses', $type, $data); } - $preCondition = ['`id` IN (' . implode(', ', $itemIds) . ')']; + $condition = ['uri-id' => $uriids]; if ($exclude_replies) { - $preCondition[] = '`id` = `parent`'; + $condition['gravity'] = GRAVITY_PARENT; } - $condition = [implode(' AND ', $preCondition)]; + $params['group_by'] = ['uri-id']; } else { $condition = ["`id` > ? " . ($exclude_replies ? " AND `id` = `parent` " : ' ') . " diff --git a/include/conversation.php b/include/conversation.php index 43eeb9e41c..a4fe9c00e3 100644 --- a/include/conversation.php +++ b/include/conversation.php @@ -22,7 +22,6 @@ use Friendica\App; use Friendica\Content\ContactSelector; use Friendica\Content\Feature; -use Friendica\Content\Pager; use Friendica\Content\Text\BBCode; use Friendica\Core\Hook; use Friendica\Core\Logger; @@ -34,7 +33,7 @@ use Friendica\DI; use Friendica\Model\Contact; use Friendica\Model\Item; use Friendica\Model\Profile; -use Friendica\Model\Term; +use Friendica\Model\Tag; use Friendica\Object\Post; use Friendica\Object\Thread; use Friendica\Protocol\Activity; @@ -527,7 +526,7 @@ function conversation(App $a, array $items, $mode, $update, $preview = false, $o $profile_name = $item['author-link']; } - $tags = Term::populateTagsFromItem($item); + $tags = Tag::populateFromItem($item); $author = ['uid' => 0, 'id' => $item['author-id'], 'network' => $item['author-network'], 'url' => $item['author-link']]; diff --git a/mod/item.php b/mod/item.php index 5785954bce..30b5f5aefa 100644 --- a/mod/item.php +++ b/mod/item.php @@ -29,7 +29,6 @@ */ use Friendica\App; -use Friendica\Content\Pager; use Friendica\Content\Text\BBCode; use Friendica\Core\Hook; use Friendica\Core\Logger; @@ -47,7 +46,6 @@ use Friendica\Model\Item; use Friendica\Model\Notify\Type; use Friendica\Model\Photo; use Friendica\Model\Tag; -use Friendica\Model\Term; use Friendica\Network\HTTPException; use Friendica\Object\EMail\ItemCCEMail; use Friendica\Protocol\Activity; @@ -102,7 +100,7 @@ function item_post(App $a) { $toplevel_item_id = intval($_REQUEST['parent'] ?? 0); $thr_parent_uri = trim($_REQUEST['parent_uri'] ?? ''); - $thread_parent_id = 0; + $thread_parent_uriid = 0; $thread_parent_contact = null; $toplevel_item = null; @@ -124,7 +122,7 @@ function item_post(App $a) { // if this isn't the top-level parent of the conversation, find it if (DBA::isResult($toplevel_item)) { // The URI and the contact is taken from the direct parent which needn't to be the top parent - $thread_parent_id = $toplevel_item['id']; + $thread_parent_uriid = $toplevel_item['uri-id']; $thr_parent_uri = $toplevel_item['uri']; $thread_parent_contact = Contact::getDetailsByURL($toplevel_item["author-link"]); @@ -382,8 +380,8 @@ function item_post(App $a) { $tags = BBCode::getTags($body); - if ($thread_parent_id && !\Friendica\Content\Feature::isEnabled($uid, 'explicit_mentions')) { - $tags = item_add_implicit_mentions($tags, $thread_parent_contact, $thread_parent_id); + if ($thread_parent_uriid && !\Friendica\Content\Feature::isEnabled($uid, 'explicit_mentions')) { + $tags = item_add_implicit_mentions($tags, $thread_parent_contact, $thread_parent_uriid); } $tagged = []; @@ -396,7 +394,7 @@ function item_post(App $a) { foreach ($tags as $tag) { $tag_type = substr($tag, 0, 1); - if ($tag_type == Term::TAG_CHARACTER[Term::HASHTAG]) { + if ($tag_type == Tag::TAG_CHARACTER[Tag::HASHTAG]) { continue; } @@ -421,9 +419,9 @@ function item_post(App $a) { $tagged[] = $tag; } // When the forum is private or the forum is addressed with a "!" make the post private - if (is_array($success['contact']) && (!empty($success['contact']['prv']) || ($tag_type == Term::TAG_CHARACTER[Term::EXCLUSIVE_MENTION]))) { + if (is_array($success['contact']) && (!empty($success['contact']['prv']) || ($tag_type == Tag::TAG_CHARACTER[Tag::EXCLUSIVE_MENTION]))) { $private_forum = $success['contact']['prv']; - $only_to_forum = ($tag_type == Term::TAG_CHARACTER[Term::EXCLUSIVE_MENTION]); + $only_to_forum = ($tag_type == Tag::TAG_CHARACTER[Tag::EXCLUSIVE_MENTION]); $private_id = $success['contact']['id']; $forum_contact = $success['contact']; } elseif (is_array($success['contact']) && !empty($success['contact']['forum']) && @@ -907,7 +905,7 @@ function handle_tag(&$body, &$inform, &$str_tags, $profile_uid, $tag, $network = $r = null; //is it a person tag? - if (Term::isType($tag, Term::MENTION, Term::IMPLICIT_MENTION, Term::EXCLUSIVE_MENTION)) { + if (Tag::isType($tag, Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION)) { $tag_type = substr($tag, 0, 1); //is it already replaced? if (strpos($tag, '[url=')) { @@ -1045,12 +1043,12 @@ function handle_tag(&$body, &$inform, &$str_tags, $profile_uid, $tag, $network = return ['replaced' => $replaced, 'contact' => $contact]; } -function item_add_implicit_mentions(array $tags, array $thread_parent_contact, $thread_parent_id) +function item_add_implicit_mentions(array $tags, array $thread_parent_contact, $thread_parent_uriid) { if (DI::config()->get('system', 'disable_implicit_mentions')) { // Add a tag if the parent contact is from ActivityPub or OStatus (This will notify them) if (in_array($thread_parent_contact['network'], [Protocol::OSTATUS, Protocol::ACTIVITYPUB])) { - $contact = Term::TAG_CHARACTER[Term::MENTION] . '[url=' . $thread_parent_contact['url'] . ']' . $thread_parent_contact['nick'] . '[/url]'; + $contact = Tag::TAG_CHARACTER[Tag::MENTION] . '[url=' . $thread_parent_contact['url'] . ']' . $thread_parent_contact['nick'] . '[/url]'; if (!stripos(implode($tags), '[url=' . $thread_parent_contact['url'] . ']')) { $tags[] = $contact; } @@ -1060,15 +1058,15 @@ function item_add_implicit_mentions(array $tags, array $thread_parent_contact, $ $thread_parent_contact['url'] => $thread_parent_contact['nick'] ]; - $parent_terms = Term::tagArrayFromItemId($thread_parent_id, [Term::MENTION, Term::IMPLICIT_MENTION]); + $parent_terms = Tag::getByURIId($thread_parent_uriid, [Tag::MENTION, Tag::IMPLICIT_MENTION]); foreach ($parent_terms as $parent_term) { - $implicit_mentions[$parent_term['url']] = $parent_term['term']; + $implicit_mentions[$parent_term['url']] = $parent_term['name']; } foreach ($implicit_mentions as $url => $label) { if ($url != \Friendica\Model\Profile::getMyURL() && !stripos(implode($tags), '[url=' . $url . ']')) { - $tags[] = Term::TAG_CHARACTER[Term::IMPLICIT_MENTION] . '[url=' . $url . ']' . $label . '[/url]'; + $tags[] = Tag::TAG_CHARACTER[Tag::IMPLICIT_MENTION] . '[url=' . $url . ']' . $label . '[/url]'; } } } diff --git a/mod/network.php b/mod/network.php index 6c02c4f843..9ec8c95b92 100644 --- a/mod/network.php +++ b/mod/network.php @@ -787,14 +787,13 @@ function networkThreadedView(App $a, $update, $parent) } $items = DBA::p("SELECT `item`.`parent-uri` AS `uri`, 0 AS `item_id`, `item`.$ordering AS `order_date`, `author`.`url` AS `author-link` FROM `item` - STRAIGHT_JOIN (SELECT `oid` FROM `term` WHERE `term` IN - (SELECT SUBSTR(`term`, 2) FROM `search` WHERE `uid` = ? AND `term` LIKE '#%') AND `otype` = ? AND `type` = ? AND `uid` = 0) AS `term` - ON `item`.`id` = `term`.`oid` + STRAIGHT_JOIN (SELECT `uri-id` FROM `tag-search-view` WHERE `name` IN + (SELECT SUBSTR(`term`, 2) FROM `search` WHERE `uid` = ? AND `term` LIKE '#%') AND `uid` = 0) AS `tag-search` + ON `item`.`uri-id` = `tag-search`.`uri-id` STRAIGHT_JOIN `contact` AS `author` ON `author`.`id` = `item`.`author-id` WHERE `item`.`uid` = 0 AND `item`.$ordering < ? AND `item`.$ordering > ? AND `item`.`gravity` = ? AND NOT `author`.`hidden` AND NOT `author`.`blocked`" . $sql_tag_nets, - local_user(), Term::OBJECT_TYPE_POST, Term::HASHTAG, - $top_limit, $bottom_limit, GRAVITY_PARENT); + local_user(), $top_limit, $bottom_limit, GRAVITY_PARENT); $data = DBA::toArray($items); diff --git a/mod/tagger.php b/mod/tagger.php index b3ba472eab..f39c37103a 100644 --- a/mod/tagger.php +++ b/mod/tagger.php @@ -183,7 +183,7 @@ EOT; q("INSERT INTO term (oid, otype, type, term, url, uid) VALUE (%d, %d, %d, '%s', '%s', %d)", intval($item['id']), $term_objtype, - Term::HASHTAG, + Tag::HASHTAG, DBA::escape($term), '', intval($owner_uid) @@ -205,7 +205,7 @@ EOT; q("INSERT INTO term (`oid`, `otype`, `type`, `term`, `url`, `uid`) VALUE (%d, %d, %d, '%s', '%s', %d)", intval($original_item['id']), $term_objtype, - Term::HASHTAG, + Tag::HASHTAG, DBA::escape($term), '', intval($owner_uid) diff --git a/src/Content/Widget/TagCloud.php b/src/Content/Widget/TagCloud.php index 5bdf7d8346..109940a126 100644 --- a/src/Content/Widget/TagCloud.php +++ b/src/Content/Widget/TagCloud.php @@ -25,7 +25,7 @@ use Friendica\Core\Renderer; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Item; -use Friendica\Model\Term; +use Friendica\Model\Tag; /** * TagCloud widget @@ -46,7 +46,7 @@ class TagCloud * @return string HTML formatted output. * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ - public static function getHTML($uid, $count = 0, $owner_id = 0, $flags = '', $type = Term::HASHTAG) + public static function getHTML($uid, $count = 0, $owner_id = 0, $flags = '', $type = Tag::HASHTAG) { $o = ''; $r = self::tagadelic($uid, $count, $owner_id, $flags, $type); @@ -85,7 +85,7 @@ class TagCloud * @return array Alphabetical sorted array of used tags of an user. * @throws \Exception */ - private static function tagadelic($uid, $count = 0, $owner_id = 0, $flags = '', $type = Term::HASHTAG) + private static function tagadelic($uid, $count = 0, $owner_id = 0, $flags = '', $type = Tag::HASHTAG) { $sql_options = Item::getPermissionsSQLByUserId($uid); $limit = $count ? sprintf('LIMIT %d', intval($count)) : ''; @@ -101,16 +101,13 @@ class TagCloud } // Fetch tags - $tag_stmt = DBA::p("SELECT `term`, COUNT(`term`) AS `total` FROM `term` - LEFT JOIN `item` ON `term`.`oid` = `item`.`id` - WHERE `term`.`uid` = ? AND `term`.`type` = ? - AND `term`.`otype` = ? + $tag_stmt = DBA::p("SELECT `name`, COUNT(`name`) AS `total` FROM `tag-search-view` + LEFT JOIN `item` ON `tag-search-view`.`uri-id` = `item`.`uri-id` + WHERE `tag-search-view`.`uid` = ? AND `item`.`visible` AND NOT `item`.`deleted` AND NOT `item`.`moderated` $sql_options - GROUP BY `term` ORDER BY `total` DESC $limit", - $uid, - $type, - Term::OBJECT_TYPE_POST + GROUP BY `name` ORDER BY `total` DESC $limit", + $uid ); if (!DBA::isResult($tag_stmt)) { return []; @@ -139,7 +136,7 @@ class TagCloud } foreach ($arr as $rr) { - $tags[$x][0] = $rr['term']; + $tags[$x][0] = $rr['name']; $tags[$x][1] = log($rr['total']); $tags[$x][2] = 0; $min = min($min, $tags[$x][1]); diff --git a/src/Content/Widget/TrendingTags.php b/src/Content/Widget/TrendingTags.php index 9f935e6de6..9c24d1549c 100644 --- a/src/Content/Widget/TrendingTags.php +++ b/src/Content/Widget/TrendingTags.php @@ -23,7 +23,7 @@ namespace Friendica\Content\Widget; use Friendica\Core\Renderer; use Friendica\DI; -use Friendica\Model\Term; +use Friendica\Model\Tag; /** * Trending tags aside widget for the community pages, handles both local and global scopes @@ -41,9 +41,9 @@ class TrendingTags public static function getHTML($content = 'global', int $period = 24) { if ($content == 'local') { - $tags = Term::getLocalTrendingHashtags($period, 20); + $tags = Tag::getLocalTrendingHashtags($period, 20); } else { - $tags = Term::getGlobalTrendingHashtags($period, 20); + $tags = Tag::getGlobalTrendingHashtags($period, 20); } $tpl = Renderer::getMarkupTemplate('widget/trending_tags.tpl'); diff --git a/src/Database/PostUpdate.php b/src/Database/PostUpdate.php index cd7e8b9462..9ae7691d5f 100644 --- a/src/Database/PostUpdate.php +++ b/src/Database/PostUpdate.php @@ -28,7 +28,9 @@ use Friendica\Model\Contact; use Friendica\Model\Item; use Friendica\Model\ItemURI; use Friendica\Model\PermissionSet; +use Friendica\Model\Tag; use Friendica\Model\UserItem; +use Friendica\Util\Strings; /** * These database-intensive post update routines are meant to be executed in the background by the cronjob. @@ -64,6 +66,12 @@ class PostUpdate if (!self::update1329()) { return false; } + if (!self::update1341()) { + return false; + } + if (!self::update1342()) { + return false; + } return true; } @@ -533,4 +541,130 @@ class PostUpdate return false; } + + /** + * Fill the "tag" table with tags and mentions from the body + * + * @return bool "true" when the job is done + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + private static function update1341() + { + // Was the script completed? + if (DI::config()->get('system', 'post_update_version') >= 1341) { + return true; + } + + $id = DI::config()->get('system', 'post_update_version_1341_id', 0); + + Logger::info('Start', ['item' => $id]); + + $start_id = $id; + $rows = 0; + + $items = DBA::p("SELECT `uri-id`,`body` FROM `item-content` WHERE + (`body` LIKE ? OR `body` LIKE ? OR `body` LIKE ?) AND `uri-id` >= ? + ORDER BY `uri-id` LIMIT 100000", '%#%', '%@%', '%!%', $id); + + if (DBA::errorNo() != 0) { + Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]); + return false; + } + + while ($item = DBA::fetch($items)) { + Tag::storeFromBody($item['uri-id'], $item['body'], '#!@', false); + $id = $item['uri-id']; + ++$rows; + if ($rows % 1000 == 0) { + DI::config()->set('system', 'post_update_version_1341_id', $id); + } + } + DBA::close($items); + + DI::config()->set('system', 'post_update_version_1341_id', $id); + + Logger::info('Processed', ['rows' => $rows, 'last' => $id]); + + // When there are less than 1,000 items processed this means that we reached the end + // The other entries will then be processed with the regular functionality + if ($rows < 1000) { + DI::config()->set('system', 'post_update_version', 1341); + Logger::info('Done'); + return true; + } + + return false; + } + + /** + * Fill the "tag" table with tags and mentions from the "term" table + * + * @return bool "true" when the job is done + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + private static function update1342() + { + // Was the script completed? + if (DI::config()->get('system', 'post_update_version') >= 1342) { + return true; + } + + $id = DI::config()->get('system', 'post_update_version_1342_id', 0); + + Logger::info('Start', ['item' => $id]); + + $start_id = $id; + $rows = 0; + + $terms = DBA::p("SELECT `term`.`tid`, `item`.`uri-id`, `term`.`type`, `term`.`term`, `term`.`url`, `item-content`.`body` + FROM `term` + INNER JOIN `item` ON `item`.`id` = `term`.`oid` + INNER JOIN `item-content` ON `item-content`.`uri-id` = `item`.`uri-id` + WHERE term.type IN (?, ?, ?, ?) AND `tid` >= ? ORDER BY `tid` LIMIT 100000", + Tag::HASHTAG, Tag::MENTION, Tag::EXCLUSIVE_MENTION, Tag::IMPLICIT_MENTION, $id); + + if (DBA::errorNo() != 0) { + Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]); + return false; + } + + while ($term = DBA::fetch($terms)) { + if (($term['type'] == Tag::MENTION) && !empty($term['url']) && !strstr($term['body'], $term['url'])) { + $condition = ['nurl' => Strings::normaliseLink($term['url']), 'uid' => 0, 'deleted' => false]; + $contact = DBA::selectFirst('contact', ['url', 'alias'], $condition, ['order' => ['id']]); + if (!DBA::isResult($contact)) { + $ssl_url = str_replace('http://', 'https://', $term['url']); + $condition = ['`alias` IN (?, ?, ?) AND `uid` = ? AND NOT `deleted`', $term['url'], Strings::normaliseLink($term['url']), $ssl_url, 0]; + $contact = DBA::selectFirst('contact', ['url', 'alias'], $condition, ['order' => ['id']]); + } + + if (DBA::isResult($contact) && (!strstr($term['body'], $contact['url']) && (empty($contact['alias']) || !strstr($term['body'], $contact['alias'])))) { + $term['type'] = Tag::IMPLICIT_MENTION; + } + } + + Tag::store($term['uri-id'], $term['type'], $term['term'], $term['url'], false); + + $id = $term['tid']; + ++$rows; + if ($rows % 1000 == 0) { + DI::config()->set('system', 'post_update_version_1342_id', $id); + } + } + DBA::close($terms); + + DI::config()->set('system', 'post_update_version_1342_id', $id); + + Logger::info('Processed', ['rows' => $rows, 'last' => $id]); + + // When there are less than 1,000 items processed this means that we reached the end + // The other entries will then be processed with the regular functionality + if ($rows < 1000) { + DI::config()->set('system', 'post_update_version', 1342); + Logger::info('Done'); + return true; + } + + return false; + } } diff --git a/src/Model/Item.php b/src/Model/Item.php index 97c96f6748..1096c22131 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -61,7 +61,7 @@ class Item // Field list that is used to display the items const DISPLAY_FIELDLIST = [ - 'uid', 'id', 'parent', 'uri', 'thr-parent', 'parent-uri', 'guid', 'network', 'gravity', + 'uid', 'id', 'parent', 'uri-id', 'uri', 'thr-parent', 'parent-uri', 'guid', 'network', 'gravity', 'commented', 'created', 'edited', 'received', 'verb', 'object-type', 'postopts', 'plink', 'wall', 'private', 'starred', 'origin', 'title', 'body', 'file', 'attach', 'language', 'content-warning', 'location', 'coord', 'app', 'rendered-hash', 'rendered-html', 'object', @@ -77,7 +77,7 @@ class Item ]; // Field list that is used to deliver items via the protocols - const DELIVER_FIELDLIST = ['uid', 'id', 'parent', 'uri', 'thr-parent', 'parent-uri', 'guid', + const DELIVER_FIELDLIST = ['uid', 'id', 'parent', 'uri-id', 'uri', 'thr-parent', 'parent-uri', 'guid', 'parent-guid', 'created', 'edited', 'verb', 'object-type', 'object', 'target', 'private', 'title', 'body', 'location', 'coord', 'app', 'attach', 'tag', 'deleted', 'extid', 'post-type', @@ -1674,6 +1674,11 @@ class Item // Check for hashtags in the body and repair or add hashtag links self::setHashtags($item); + // Store tags from the body if this hadn't been handled previously in the protocol classes + if (!Tag::existsForPost($item['uri-id'])) { + Tag::storeFromBody($item['uri-id'], $item['body']); + } + $item['thr-parent'] = $item['parent-uri']; $notify_type = Delivery::POST; @@ -3558,7 +3563,7 @@ class Item return $ev; } - $tags = Term::populateTagsFromItem($item); + $tags = Tag::populateFromItem($item); $item['tags'] = $tags['tags']; $item['hashtags'] = $tags['hashtags']; diff --git a/src/Model/Tag.php b/src/Model/Tag.php index cf453f2431..04107f804c 100644 --- a/src/Model/Tag.php +++ b/src/Model/Tag.php @@ -22,9 +22,11 @@ namespace Friendica\Model; use Friendica\Content\Text\BBCode; +use Friendica\Core\Cache\Duration; use Friendica\Core\Logger; use Friendica\Core\System; use Friendica\Database\DBA; +use Friendica\DI; use Friendica\Util\Strings; /** @@ -67,7 +69,19 @@ class Tag */ public static function store(int $uriid, int $type, string $name, string $url = '', $probing = true) { - $name = trim($name, "\x00..\x20\xFF#!@"); + if ($type == self::HASHTAG) { + // Remove some common "garbarge" from tags + $name = trim($name, "\x00..\x20\xFF#!@,;.:'/?!^°$%".'"'); + + $tags = explode(self::TAG_CHARACTER[self::HASHTAG], $name); + if (count($tags) > 1) { + foreach ($tags as $tag) { + self::store($uriid, $type, $tag, $url, $probing); + } + return; + } + } + if (empty($name)) { return; } @@ -75,7 +89,7 @@ class Tag $cid = 0; $tagid = 0; - if (in_array($type, [Tag::MENTION, Tag::EXCLUSIVE_MENTION, Tag::IMPLICIT_MENTION])) { + if (in_array($type, [self::MENTION, self::EXCLUSIVE_MENTION, self::IMPLICIT_MENTION])) { if (empty($url)) { // No mention without a contact url return; @@ -114,7 +128,7 @@ class Tag if (empty($cid)) { $fields = ['name' => substr($name, 0, 96), 'url' => '']; - if (($type != Tag::HASHTAG) && !empty($url) && ($url != $name)) { + if (($type != self::HASHTAG) && !empty($url) && ($url != $name)) { $fields['url'] = strtolower($url); } @@ -134,9 +148,9 @@ class Tag $fields = ['uri-id' => $uriid, 'type' => $type, 'tid' => $tagid, 'cid' => $cid]; - if (in_array($type, [Tag::MENTION, Tag::EXCLUSIVE_MENTION, Tag::IMPLICIT_MENTION])) { + if (in_array($type, [self::MENTION, self::EXCLUSIVE_MENTION, self::IMPLICIT_MENTION])) { $condition = $fields; - $condition['type'] = [Tag::MENTION, Tag::EXCLUSIVE_MENTION, Tag::IMPLICIT_MENTION]; + $condition['type'] = [self::MENTION, self::EXCLUSIVE_MENTION, self::IMPLICIT_MENTION]; if (DBA::exists('post-tag', $condition)) { Logger::info('Tag already exists', $fields); return; @@ -221,6 +235,17 @@ class Tag } } + /** + * Checks for stored hashtags and mentions for the given post + * + * @param integer $uriid + * @return bool + */ + public static function existsForPost(int $uriid) + { + return DBA::exists('post-tag', ['uri-id' => $uriid, 'type' => [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION, self::EXCLUSIVE_MENTION]]); + } + /** * Remove tag/mention * @@ -282,6 +307,176 @@ class Tag } else { return self::UNKNOWN; } - } + + /** + * Retrieves the terms from the provided type(s) associated with the provided item ID. + * + * @param int $item_id + * @param int|array $type + * @return array + * @throws \Exception + */ + public static function getByURIId(int $uri_id, array $type = [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION, self::EXCLUSIVE_MENTION]) + { + $condition = ['uri-id' => $uri_id, 'type' => $type]; + return DBA::selectToArray('tag-view', ['type', 'name', 'url'], $condition); + } + + /** + * Sorts an item's tags into mentions, hashtags and other tags. Generate personalized URLs by user and modify the + * provided item's body with them. + * + * @param array $item + * @return array + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws \ImagickException + */ + public static function populateFromItem(&$item) + { + $return = [ + 'tags' => [], + 'hashtags' => [], + 'mentions' => [], + 'implicit_mentions' => [], + ]; + + $searchpath = DI::baseUrl() . "/search?tag="; + + $taglist = DBA::select('tag-view', ['type', 'name', 'url'], + ['uri-id' => $item['uri-id'], 'type' => [self::HASHTAG, self::MENTION, self::EXCLUSIVE_MENTION, self::IMPLICIT_MENTION]]); + while ($tag = DBA::fetch($taglist)) { + if ($tag['url'] == '') { + $tag['url'] = $searchpath . rawurlencode($tag['name']); + } + + $orig_tag = $tag['url']; + + $prefix = self::TAG_CHARACTER[$tag['type']]; + switch($tag['type']) { + case self::HASHTAG: + if ($orig_tag != $tag['url']) { + $item['body'] = str_replace($orig_tag, $tag['url'], $item['body']); + } + + $return['hashtags'][] = $prefix . '' . htmlspecialchars($tag['name']) . ''; + $return['tags'][] = $prefix . '' . htmlspecialchars($tag['name']) . ''; + break; + case self::MENTION: + case self::EXCLUSIVE_MENTION: + $tag['url'] = Contact::magicLink($tag['url']); + $return['mentions'][] = $prefix . '' . htmlspecialchars($tag['name']) . ''; + $return['tags'][] = $prefix . '' . htmlspecialchars($tag['name']) . ''; + break; + case self::IMPLICIT_MENTION: + $return['implicit_mentions'][] = $prefix . $tag['name']; + break; + } + } + DBA::close($taglist); + + return $return; + } + + /** + * Search posts for given tag + * + * @param string $search + * @param integer $uid + * @param integer $start + * @param integer $limit + * @return array with URI-ID + */ + public static function getURIIdListByTag(string $search, int $uid = 0, int $start = 0, int $limit = 100) + { + $condition = ["`name` = ? AND (NOT `private` OR (`private` AND `uid` = ?))", $search, $uid]; + $params = [ + 'order' => ['uri-id' => true], + 'group_by' => ['uri-id'], + 'limit' => [$start, $limit] + ]; + + $tags = DBA::select('tag-search-view', ['uri-id'], $condition, $params); + + $uriids = []; + while ($tag = DBA::fetch($tags)) { + $uriids[] = $tag['uri-id']; + } + DBA::close($tags); + + return $uriids; + } + + /** + * Returns a list of the most frequent global hashtags over the given period + * + * @param int $period Period in hours to consider posts + * @return array + * @throws \Exception + */ + public static function getGlobalTrendingHashtags(int $period, $limit = 10) + { + $tags = DI::cache()->get('global_trending_tags'); + + if (empty($tags)) { + $tagsStmt = DBA::p("SELECT `name` AS `term`, COUNT(*) AS `score` + FROM `tag-search-view` + WHERE `private` = ? AND `received` > DATE_SUB(NOW(), INTERVAL ? HOUR) + GROUP BY `term` ORDER BY `score` DESC LIMIT ?", + Item::PUBLIC, $period, $limit); + + if (DBA::isResult($tagsStmt)) { + $tags = DBA::toArray($tagsStmt); + DI::cache()->set('global_trending_tags', $tags, Duration::HOUR); + } + } + + return $tags ?: []; + } + + /** + * Returns a list of the most frequent local hashtags over the given period + * + * @param int $period Period in hours to consider posts + * @return array + * @throws \Exception + */ + public static function getLocalTrendingHashtags(int $period, $limit = 10) + { + $tags = DI::cache()->get('local_trending_tags'); + + if (empty($tags)) { + $tagsStmt = DBA::p("SELECT `name` AS `term`, COUNT(*) AS `score` + FROM `tag-search-view` + WHERE `private` = ? AND `wall` AND `origin` AND `received` > DATE_SUB(NOW(), INTERVAL ? HOUR) + GROUP BY `term` ORDER BY `score` DESC LIMIT ?", + Item::PUBLIC, $period, $limit); + + if (DBA::isResult($tagsStmt)) { + $tags = DBA::toArray($tagsStmt); + DI::cache()->set('local_trending_tags', $tags, Duration::HOUR); + } + } + + return $tags ?: []; + } + + /** + * Check if the provided tag is of one of the provided term types. + * + * @param string $tag + * @param int ...$types + * @return bool + */ + public static function isType($tag, ...$types) + { + $tag_chars = []; + foreach ($types as $type) { + if (array_key_exists($type, self::TAG_CHARACTER)) { + $tag_chars[] = self::TAG_CHARACTER[$type]; + } + } + + return Strings::startsWith($tag, $tag_chars); + } } diff --git a/src/Model/Term.php b/src/Model/Term.php index 13639f7700..5f54024871 100644 --- a/src/Model/Term.php +++ b/src/Model/Term.php @@ -21,7 +21,6 @@ namespace Friendica\Model; -use Friendica\Core\Cache\Duration; use Friendica\Core\Logger; use Friendica\Database\DBA; use Friendica\DI; @@ -60,95 +59,6 @@ class Term const OBJECT_TYPE_POST = 1; const OBJECT_TYPE_PHOTO = 2; - /** - * Returns a list of the most frequent global hashtags over the given period - * - * @param int $period Period in hours to consider posts - * @return array - * @throws \Exception - */ - public static function getGlobalTrendingHashtags(int $period, $limit = 10) - { - $tags = DI::cache()->get('global_trending_tags'); - - if (!$tags) { - $tagsStmt = DBA::p("SELECT t.`term`, COUNT(*) AS `score` - FROM `term` t - JOIN `item` i ON i.`id` = t.`oid` AND i.`uid` = t.`uid` - JOIN `thread` ON `thread`.`iid` = i.`id` - WHERE `thread`.`visible` - AND NOT `thread`.`deleted` - AND NOT `thread`.`moderated` - AND `thread`.`private` = ? - AND t.`uid` = 0 - AND t.`otype` = ? - AND t.`type` = ? - AND t.`term` != '' - AND i.`received` > DATE_SUB(NOW(), INTERVAL ? HOUR) - GROUP BY `term` - ORDER BY `score` DESC - LIMIT ?", - Item::PUBLIC, - Term::OBJECT_TYPE_POST, - Term::HASHTAG, - $period, - $limit - ); - - if (DBA::isResult($tagsStmt)) { - $tags = DBA::toArray($tagsStmt); - DI::cache()->set('global_trending_tags', $tags, Duration::HOUR); - } - } - - return $tags ?: []; - } - - /** - * Returns a list of the most frequent local hashtags over the given period - * - * @param int $period Period in hours to consider posts - * @return array - * @throws \Exception - */ - public static function getLocalTrendingHashtags(int $period, $limit = 10) - { - $tags = DI::cache()->get('local_trending_tags'); - - if (!$tags) { - $tagsStmt = DBA::p("SELECT t.`term`, COUNT(*) AS `score` - FROM `term` t - JOIN `item` i ON i.`id` = t.`oid` AND i.`uid` = t.`uid` - JOIN `thread` ON `thread`.`iid` = i.`id` - WHERE `thread`.`visible` - AND NOT `thread`.`deleted` - AND NOT `thread`.`moderated` - AND `thread`.`private` = ? - AND `thread`.`wall` - AND `thread`.`origin` - AND t.`otype` = ? - AND t.`type` = ? - AND t.`term` != '' - AND i.`received` > DATE_SUB(NOW(), INTERVAL ? HOUR) - GROUP BY `term` - ORDER BY `score` DESC - LIMIT ?", - Item::PUBLIC, - Term::OBJECT_TYPE_POST, - Term::HASHTAG, - $period, - $limit - ); - - if (DBA::isResult($tagsStmt)) { - $tags = DBA::toArray($tagsStmt); - DI::cache()->set('local_trending_tags', $tags, Duration::HOUR); - } - } - - return $tags ?: []; - } - /** * Generates the legacy item.tag field comma-separated BBCode string from an item ID. * Includes only hashtags, implicit and explicit mentions. @@ -160,7 +70,7 @@ class Term public static function tagTextFromItemId($item_id) { $tag_list = []; - $tags = self::tagArrayFromItemId($item_id, [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION]); + $tags = self::getByItemId($item_id, [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION]); foreach ($tags as $tag) { $tag_list[] = self::TAG_CHARACTER[$tag['type']] . '[url=' . $tag['url'] . ']' . $tag['term'] . '[/url]'; } @@ -176,7 +86,7 @@ class Term * @return array * @throws \Exception */ - public static function tagArrayFromItemId($item_id, $type = [self::HASHTAG, self::MENTION]) + private static function getByItemId($item_id, $type = [self::HASHTAG, self::MENTION]) { $condition = ['otype' => self::OBJECT_TYPE_POST, 'oid' => $item_id, 'type' => $type]; $tags = DBA::select('term', ['type', 'term', 'url'], $condition); @@ -198,7 +108,7 @@ class Term public static function fileTextFromItemId($item_id) { $file_text = ''; - $tags = self::tagArrayFromItemId($item_id, [self::FILE, self::CATEGORY]); + $tags = self::getByItemId($item_id, [self::FILE, self::CATEGORY]); foreach ($tags as $tag) { if ($tag['type'] == self::CATEGORY) { $file_text .= '<' . $tag['term'] . '>'; @@ -291,7 +201,7 @@ class Term } foreach ($tags as $link => $tag) { - if (self::isType($tag, self::HASHTAG)) { + if (Tag::isType($tag, self::HASHTAG)) { // try to ignore #039 or #1 or anything like that if (ctype_digit(substr(trim($tag), 1))) { continue; @@ -305,8 +215,8 @@ class Term $type = self::HASHTAG; $term = substr($tag, 1); $link = ''; - } elseif (self::isType($tag, self::MENTION, self::EXCLUSIVE_MENTION, self::IMPLICIT_MENTION)) { - if (self::isType($tag, self::MENTION, self::EXCLUSIVE_MENTION)) { + } elseif (Tag::isType($tag, self::MENTION, self::EXCLUSIVE_MENTION, self::IMPLICIT_MENTION)) { + if (Tag::isType($tag, self::MENTION, self::EXCLUSIVE_MENTION)) { $type = self::MENTION; } else { $type = self::IMPLICIT_MENTION; @@ -355,7 +265,7 @@ class Term ]); // Search for mentions - if (self::isType($tag, self::MENTION, self::EXCLUSIVE_MENTION) + if (Tag::isType($tag, self::MENTION, self::EXCLUSIVE_MENTION) && ( strpos($link, $profile_base_friendica) !== false || strpos($link, $profile_base_diaspora) !== false @@ -424,64 +334,6 @@ class Term } } - /** - * Sorts an item's tags into mentions, hashtags and other tags. Generate personalized URLs by user and modify the - * provided item's body with them. - * - * @param array $item - * @return array - * @throws \Friendica\Network\HTTPException\InternalServerErrorException - * @throws \ImagickException - */ - public static function populateTagsFromItem(&$item) - { - $return = [ - 'tags' => [], - 'hashtags' => [], - 'mentions' => [], - 'implicit_mentions' => [], - ]; - - $searchpath = DI::baseUrl() . "/search?tag="; - - $taglist = DBA::select( - 'term', - ['type', 'term', 'url'], - ['otype' => self::OBJECT_TYPE_POST, 'oid' => $item['id'], 'type' => [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION]], - ['order' => ['tid']] - ); - while ($tag = DBA::fetch($taglist)) { - if ($tag['url'] == '') { - $tag['url'] = $searchpath . rawurlencode($tag['term']); - } - - $orig_tag = $tag['url']; - - $prefix = self::TAG_CHARACTER[$tag['type']]; - switch($tag['type']) { - case self::HASHTAG: - if ($orig_tag != $tag['url']) { - $item['body'] = str_replace($orig_tag, $tag['url'], $item['body']); - } - - $return['hashtags'][] = $prefix . '' . htmlspecialchars($tag['term']) . ''; - $return['tags'][] = $prefix . '' . htmlspecialchars($tag['term']) . ''; - break; - case self::MENTION: - $tag['url'] = Contact::magicLink($tag['url']); - $return['mentions'][] = $prefix . '' . htmlspecialchars($tag['term']) . ''; - $return['tags'][] = $prefix . '' . htmlspecialchars($tag['term']) . ''; - break; - case self::IMPLICIT_MENTION: - $return['implicit_mentions'][] = $prefix . $tag['term']; - break; - } - } - DBA::close($taglist); - - return $return; - } - /** * Delete tags of the specific type(s) from an item * @@ -498,23 +350,4 @@ class Term // Clean up all tags DBA::delete('term', ['otype' => self::OBJECT_TYPE_POST, 'oid' => $item_id, 'type' => $type]); } - - /** - * Check if the provided tag is of one of the provided term types. - * - * @param string $tag - * @param int ...$types - * @return bool - */ - public static function isType($tag, ...$types) - { - $tag_chars = []; - foreach ($types as $type) { - if (array_key_exists($type, self::TAG_CHARACTER)) { - $tag_chars[] = self::TAG_CHARACTER[$type]; - } - } - - return Strings::startsWith($tag, $tag_chars); - } } diff --git a/src/Model/UserItem.php b/src/Model/UserItem.php index 72db1005d1..50e23e1580 100644 --- a/src/Model/UserItem.php +++ b/src/Model/UserItem.php @@ -26,7 +26,7 @@ use Friendica\Core\Hook; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Util\Strings; -use Friendica\Model\Term; +use Friendica\Model\Tag; class UserItem { @@ -50,7 +50,7 @@ class UserItem */ public static function setNotification(int $iid) { - $fields = ['id', 'uid', 'body', 'parent', 'gravity', 'tag', 'contact-id', 'thr-parent', 'parent-uri', 'author-id']; + $fields = ['id', 'uri-id', 'uid', 'body', 'parent', 'gravity', 'tag', 'contact-id', 'thr-parent', 'parent-uri', 'author-id']; $item = Item::selectFirst($fields, ['id' => $iid, 'origin' => false]); if (!DBA::isResult($item)) { return; @@ -207,7 +207,7 @@ class UserItem } // Or the contact is a mentioned forum - $tags = DBA::select('term', ['url'], ['otype' => Term::OBJECT_TYPE_POST, 'oid' => $item['id'], 'type' => Term::MENTION, 'uid' => $uid]); + $tags = DBA::select('tag-view', ['url'], ['uri-id' => $item['uri-id'], 'type' => [Tag::MENTION, Tag::EXCLUSIVE_MENTION]]); while ($tag = DBA::fetch($tags)) { $condition = ['nurl' => Strings::normaliseLink($tag['url']), 'uid' => $uid, 'notify_new_posts' => true, 'contact-type' => Contact::TYPE_COMMUNITY]; if (DBA::exists('contact', $condition)) { diff --git a/src/Module/Admin/Item/Source.php b/src/Module/Admin/Item/Source.php index b8aaff99b8..e35eafd2f2 100644 --- a/src/Module/Admin/Item/Source.php +++ b/src/Module/Admin/Item/Source.php @@ -48,14 +48,14 @@ class Source extends BaseAdmin $item_id = ''; $terms = []; if (!empty($guid)) { - $item = Model\Item::selectFirst(['id', 'guid', 'uri'], ['guid' => $guid]); + $item = Model\Item::selectFirst(['id', 'uri-id', 'guid', 'uri'], ['guid' => $guid]); $conversation = Model\Conversation::getByItemUri($item['uri']); $item_id = $item['id']; $item_uri = $item['uri']; $source = $conversation['source']; - $terms = Model\Term::tagArrayFromItemId($item['id'], [Model\Term::HASHTAG, Model\Term::MENTION, Model\Term::IMPLICIT_MENTION]); + $terms = Model\Tag::getByURIId($item['uri-id'], [Model\Tag::HASHTAG, Model\Tag::MENTION, Model\Tag::IMPLICIT_MENTION]); } $tpl = Renderer::getMarkupTemplate('admin/item/source.tpl'); diff --git a/src/Module/Hashtag.php b/src/Module/Hashtag.php index 06c6374e34..365e77572c 100644 --- a/src/Module/Hashtag.php +++ b/src/Module/Hashtag.php @@ -25,14 +25,12 @@ use Friendica\BaseModule; use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\Util\Strings; -use Friendica\Model\Term; /** * Hashtag module. */ class Hashtag extends BaseModule { - public static function content(array $parameters = []) { $result = []; @@ -42,12 +40,9 @@ class Hashtag extends BaseModule System::jsonExit($result); } - $taglist = DBA::p("SELECT DISTINCT(`term`) FROM `term` WHERE `term` LIKE ? AND `type` = ? ORDER BY `term`", - $t . '%', - intval(Term::HASHTAG) - ); + $taglist = DBA::select('tag', ['name'], ["`name` LIKE ?", $t . "%"], ['order' => ['name'], 'limit' => 100]); while ($tag = DBA::fetch($taglist)) { - $result[] = ['text' => $tag['term']]; + $result[] = ['text' => $tag['name']]; } DBA::close($taglist); diff --git a/src/Module/Profile/Profile.php b/src/Module/Profile/Profile.php index 6c7f4f14e5..c187281d33 100644 --- a/src/Module/Profile/Profile.php +++ b/src/Module/Profile/Profile.php @@ -35,7 +35,7 @@ use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Contact; use Friendica\Model\Profile as ProfileModel; -use Friendica\Model\Term; +use Friendica\Model\Tag; use Friendica\Model\User; use Friendica\Module\BaseProfile; use Friendica\Module\Security\Login; @@ -184,7 +184,7 @@ class Profile extends BaseProfile foreach (explode(',', $a->profile['pub_keywords']) as $tag_label) { $tags[] = [ 'url' => '/search?tag=' . $tag_label, - 'label' => Term::TAG_CHARACTER[Term::HASHTAG] . $tag_label, + 'label' => Tag::TAG_CHARACTER[Tag::HASHTAG] . $tag_label, ]; } diff --git a/src/Module/Profile/Status.php b/src/Module/Profile/Status.php index 8b6734e5cd..685ab4e076 100644 --- a/src/Module/Profile/Status.php +++ b/src/Module/Profile/Status.php @@ -147,8 +147,8 @@ class Status extends BaseProfile } if (!empty($hashtags)) { - $sql_post_table .= sprintf("INNER JOIN (SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d ORDER BY `tid` DESC) AS `term` ON `item`.`id` = `term`.`oid` ", - DBA::escape(Strings::protectSprintf($hashtags)), intval(Term::OBJECT_TYPE_POST), intval(Term::HASHTAG), intval($a->profile['uid'])); + $sql_post_table .= sprintf("INNER JOIN (SELECT `uri-id` FROM `tag-search-view` WHERE `name` = '%s' AND `uid` = %d ORDER BY `uri-id` DESC) AS `tag-search` ON `item`.`uri-id` = `tag-search`.`uri-id` ", + DBA::escape(Strings::protectSprintf($hashtags)), intval($a->profile['uid'])); } if (!empty($datequery)) { diff --git a/src/Module/Search/Index.php b/src/Module/Search/Index.php index 44407623e9..27074fa82a 100644 --- a/src/Module/Search/Index.php +++ b/src/Module/Search/Index.php @@ -33,7 +33,7 @@ use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Contact; use Friendica\Model\Item; -use Friendica\Model\Term; +use Friendica\Model\Tag; use Friendica\Module\BaseSearch; use Friendica\Network\HTTPException; use Friendica\Util\Strings; @@ -149,28 +149,11 @@ class Index extends BaseSearch if ($tag) { Logger::info('Start tag search.', ['q' => $search]); + $uriids = Tag::getURIIdListByTag($search, local_user(), $pager->getStart(), $pager->getItemsPerPage()); - $condition = [ - "(`uid` = 0 OR (`uid` = ? AND NOT `global`)) - AND `otype` = ? AND `type` = ? AND `term` = ?", - local_user(), Term::OBJECT_TYPE_POST, Term::HASHTAG, $search - ]; - $params = [ - 'order' => ['received' => true], - 'limit' => [$pager->getStart(), $pager->getItemsPerPage()] - ]; - $terms = DBA::select('term', ['oid'], $condition, $params); - - $itemids = []; - while ($term = DBA::fetch($terms)) { - $itemids[] = $term['oid']; - } - - DBA::close($terms); - - if (!empty($itemids)) { - $params = ['order' => ['id' => true]]; - $items = Item::selectForUser(local_user(), [], ['id' => $itemids], $params); + if (!empty($uriids)) { + $params = ['order' => ['id' => true], 'group_by' => ['uri-id']]; + $items = Item::selectForUser(local_user(), [], ['uri-id' => $uriids], $params); $r = Item::inArray($items); } else { $r = []; diff --git a/src/Object/Post.php b/src/Object/Post.php index 76cf6b0367..8488df000f 100644 --- a/src/Object/Post.php +++ b/src/Object/Post.php @@ -33,7 +33,7 @@ use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Contact; use Friendica\Model\Item; -use Friendica\Model\Term; +use Friendica\Model\Tag; use Friendica\Model\User; use Friendica\Protocol\Activity; use Friendica\Util\Crypto; @@ -390,7 +390,7 @@ class Post $buttons["like"] = false; } - $tags = Term::populateTagsFromItem($item); + $tags = Tag::populateFromItem($item); $ago = Temporal::getRelativeDate($item['created']); $ago_received = Temporal::getRelativeDate($item['received']); @@ -860,7 +860,7 @@ class Post return ''; } - $item = Item::selectFirst(['author-addr'], ['id' => $this->getId()]); + $item = Item::selectFirst(['author-addr', 'uri-id'], ['id' => $this->getId()]); if (!DBA::isResult($item) || empty($item['author-addr'])) { // Should not happen return ''; @@ -872,7 +872,7 @@ class Post $text = ''; } - $terms = Term::tagArrayFromItemId($this->getId(), [Term::MENTION, Term::IMPLICIT_MENTION]); + $terms = Tag::getByURIId($item['uri-id'], [Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION]); foreach ($terms as $term) { $profile = Contact::getDetailsByURL($term['url']); if (!empty($profile['addr']) && ((($profile['contact-type'] ?? '') ?: Contact::TYPE_UNKNOWN) != Contact::TYPE_COMMUNITY) && diff --git a/src/Protocol/ActivityPub/Processor.php b/src/Protocol/ActivityPub/Processor.php index 7860223432..43343f1e6b 100644 --- a/src/Protocol/ActivityPub/Processor.php +++ b/src/Protocol/ActivityPub/Processor.php @@ -35,7 +35,6 @@ use Friendica\Model\Item; use Friendica\Model\ItemURI; use Friendica\Model\Mail; use Friendica\Model\Tag; -use Friendica\Model\Term; use Friendica\Model\User; use Friendica\Protocol\Activity; use Friendica\Protocol\ActivityPub; @@ -1016,7 +1015,7 @@ class Processor return []; } - $parent_terms = Term::tagArrayFromItemId($parent['id'], [Term::MENTION, Term::IMPLICIT_MENTION]); + $parent_terms = Tag::getByURIId($parent['uri-id'], [Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION]); $parent_author = Contact::getDetailsByURL($parent['author-link'], 0); @@ -1084,8 +1083,8 @@ class Processor foreach ($activity_tags as $index => $tag) { if (in_array($tag['href'], $potential_mentions)) { $activity_tags[$index]['name'] = preg_replace( - '/' . preg_quote(Term::TAG_CHARACTER[Term::MENTION], '/') . '/', - Term::TAG_CHARACTER[Term::IMPLICIT_MENTION], + '/' . preg_quote(Tag::TAG_CHARACTER[Tag::MENTION], '/') . '/', + Tag::TAG_CHARACTER[Tag::IMPLICIT_MENTION], $activity_tags[$index]['name'], 1 ); diff --git a/src/Protocol/ActivityPub/Transmitter.php b/src/Protocol/ActivityPub/Transmitter.php index 8928b9435a..16b7b039a9 100644 --- a/src/Protocol/ActivityPub/Transmitter.php +++ b/src/Protocol/ActivityPub/Transmitter.php @@ -34,9 +34,10 @@ use Friendica\Model\APContact; use Friendica\Model\Contact; use Friendica\Model\Conversation; use Friendica\Model\Item; +use Friendica\Model\ItemURI; use Friendica\Model\Profile; use Friendica\Model\Photo; -use Friendica\Model\Term; +use Friendica\Model\Tag; use Friendica\Model\User; use Friendica\Protocol\Activity; use Friendica\Protocol\ActivityPub; @@ -407,7 +408,7 @@ class Transmitter $actor_profile = APContact::getByURL($item['author-link']); } - $terms = Term::tagArrayFromItemId($item['id'], [Term::MENTION, Term::IMPLICIT_MENTION]); + $terms = Tag::getByURIId($item['uri-id'], [Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION]); if ($item['private'] != Item::PRIVATE) { // Directly mention the original author upon a quoted reshare. @@ -701,6 +702,8 @@ class Transmitter return []; } + $mail['uri-id'] = ItemURI::insert(['uri' => $mail['uri'], 'guid' => $mail['guid']]); + $reply = DBA::selectFirst('mail', ['uri'], ['parent-uri' => $mail['parent-uri'], 'reply' => false]); // Making the post more compatible for Mastodon by: @@ -1009,12 +1012,12 @@ class Transmitter { $tags = []; - $terms = Term::tagArrayFromItemId($item['id'], [Term::HASHTAG, Term::MENTION, Term::IMPLICIT_MENTION]); + $terms = Tag::getByURIId($item['uri-id'], [Tag::HASHTAG, Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION]); foreach ($terms as $term) { - if ($term['type'] == Term::HASHTAG) { - $url = DI::baseUrl() . '/search?tag=' . urlencode($term['term']); - $tags[] = ['type' => 'Hashtag', 'href' => $url, 'name' => '#' . $term['term']]; - } elseif ($term['type'] == Term::MENTION || $term['type'] == Term::IMPLICIT_MENTION) { + if ($term['type'] == Tag::HASHTAG) { + $url = DI::baseUrl() . '/search?tag=' . urlencode($term['name']); + $tags[] = ['type' => 'Hashtag', 'href' => $url, 'name' => '#' . $term['name']]; + } else { $contact = Contact::getDetailsByURL($term['url']); if (!empty($contact['addr'])) { $mention = '@' . $contact['addr']; @@ -1213,15 +1216,14 @@ class Transmitter /** * Returns if the post contains sensitive content ("nsfw") * - * @param integer $item_id + * @param integer $uri_id * * @return boolean * @throws \Exception */ - private static function isSensitive($item_id) + private static function isSensitive($uri_id) { - $condition = ['otype' => Term::OBJECT_TYPE_POST, 'oid' => $item_id, 'type' => Term::HASHTAG, 'term' => 'nsfw']; - return DBA::exists('term', $condition); + return DBA::exists('tag-view', ['uri-id' => $uri_id, 'name' => 'nsfw']); } /** @@ -1303,7 +1305,7 @@ class Transmitter $data['url'] = $item['plink']; $data['attributedTo'] = $item['author-link']; - $data['sensitive'] = self::isSensitive($item['id']); + $data['sensitive'] = self::isSensitive($item['uri-id']); $data['context'] = self::fetchContextURLForItem($item); if (!empty($item['title'])) { diff --git a/src/Protocol/Diaspora.php b/src/Protocol/Diaspora.php index 6282295011..a91c5cdf80 100644 --- a/src/Protocol/Diaspora.php +++ b/src/Protocol/Diaspora.php @@ -38,9 +38,7 @@ use Friendica\Model\Item; use Friendica\Model\ItemURI; use Friendica\Model\ItemDeliveryData; use Friendica\Model\Mail; -use Friendica\Model\Profile; use Friendica\Model\Tag; -use Friendica\Model\Term; use Friendica\Model\User; use Friendica\Network\Probe; use Friendica\Util\Crypto; @@ -114,7 +112,7 @@ class Diaspora if (DI::config()->get("system", "relay_directly", false)) { // We distribute our stuff based on the parent to ensure that the thread will be complete - $parent = Item::selectFirst(['parent'], ['id' => $item_id]); + $parent = Item::selectFirst(['uri-id'], ['id' => $item_id]); if (!DBA::isResult($parent)) { return; } @@ -127,11 +125,10 @@ class Diaspora DBA::close($servers); // All tags of the current post - $condition = ['otype' => Term::OBJECT_TYPE_POST, 'type' => Term::HASHTAG, 'oid' => $parent['parent']]; - $tags = DBA::select('term', ['term'], $condition); + $tags = DBA::select('tag-view', ['name'], ['uri-id' => $parent['uri-id'], 'type' => Tag::HASHTAG]); $taglist = []; while ($tag = DBA::fetch($tags)) { - $taglist[] = $tag['term']; + $taglist[] = $tag['name']; } DBA::close($tags); diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php index 7ba2c38a36..2c3a9fb5fd 100755 --- a/static/dbstructure.config.php +++ b/static/dbstructure.config.php @@ -51,7 +51,7 @@ use Friendica\Database\DBA; if (!defined('DB_UPDATE_VERSION')) { - define('DB_UPDATE_VERSION', 1341); + define('DB_UPDATE_VERSION', 1343); } return [ @@ -754,6 +754,7 @@ return [ "icid" => ["icid"], "iaid" => ["iaid"], "psid_wall" => ["psid", "wall"], + "uri-id" => ["uri-id"], ] ], "item-activity" => [ diff --git a/static/dbview.config.php b/static/dbview.config.php index d6ea944a44..0f0d691085 100755 --- a/static/dbview.config.php +++ b/static/dbview.config.php @@ -220,6 +220,25 @@ return [ INNER JOIN `contact` ON `register`.`uid` = `contact`.`uid` INNER JOIN `user` ON `register`.`uid` = `user`.`uid`" ], + "tag-search-view" => [ + "fields" => [ + "uri-id" => ["post-tag", "uri-id"], + "iid" => ["item", "id"], + "uri" => ["item", "uri"], + "guid" => ["item", "guid"], + "uid" => ["item", "uid"], + "private" => ["item", "private"], + "wall" => ["item", "wall"], + "origin" => ["item", "origin"], + "gravity" => ["item", "gravity"], + "received" => ["item", "received"], + "name" => ["tag", "name"], + ], + "query" => "FROM `post-tag` + INNER JOIN `tag` ON `tag`.`id` = `post-tag`.`tid` + INNER JOIN `item` ON `item`.`uri-id` = `post-tag`.`uri-id` + WHERE `post-tag`.`type` = 1" + ], "workerqueue-view" => [ "fields" => [ "pid" => ["process", "pid"], diff --git a/view/templates/admin/item/source.tpl b/view/templates/admin/item/source.tpl index 4f985cbdce..a681d8739f 100644 --- a/view/templates/admin/item/source.tpl +++ b/view/templates/admin/item/source.tpl @@ -45,7 +45,7 @@ {{if $term.type == 8}}Implicit Mention{{/if}} - {{$term.term}} + {{$term.name}} {{$term.url}}