X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FModel%2FTag.php;h=2bdbbc8aca1351400a4ffd3dbba330af29fdab34;hb=f9994548c1f1110c7f548e00fcf1b6ee42b9de3b;hp=1186602f549b4277d472adb0af115b88aef3968b;hpb=5367620467f690774966c77cf5049ace9e6552a8;p=friendica.git diff --git a/src/Model/Tag.php b/src/Model/Tag.php index 1186602f54..2bdbbc8aca 100644 --- a/src/Model/Tag.php +++ b/src/Model/Tag.php @@ -22,8 +22,10 @@ namespace Friendica\Model; use Friendica\Content\Text\BBCode; +use Friendica\Core\Cache\Duration; use Friendica\Core\Logger; use Friendica\Core\System; +use Friendica\Database\Database; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Util\Strings; @@ -39,8 +41,6 @@ class Tag const UNKNOWN = 0; const HASHTAG = 1; const MENTION = 2; - const CATEGORY = 3; - const FILE = 5; /** * An implicit mention is a mention in a comment body that is redundant with the threading information. */ @@ -68,7 +68,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; } @@ -76,12 +88,16 @@ 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; } + if ((substr($url, 0, 7) == 'https//') || (substr($url, 0, 6) == 'http//')) { + Logger::notice('Wrong scheme in url', ['url' => $url, 'callstack' => System::callstack(20)]); + } + if (!$probing) { $condition = ['nurl' => Strings::normaliseLink($url), 'uid' => 0, 'deleted' => false]; $contact = DBA::selectFirst('contact', ['id'], $condition, ['order' => ['id']]); @@ -100,7 +116,7 @@ class Tag } } } else { - $cid = Contact::getIdForURL($url, 0, true); + $cid = Contact::getIdForURL($url, 0, false); Logger::info('Got id by probing', ['cid' => $cid, 'url' => $url]); } @@ -113,42 +129,60 @@ class Tag } if (empty($cid)) { - $fields = ['name' => substr($name, 0, 96), 'url' => '']; - - if (($type != Tag::HASHTAG) && !empty($url) && ($url != $name)) { - $fields['url'] = strtolower($url); - } - - $tag = DBA::selectFirst('tag', ['id'], $fields); - if (!DBA::isResult($tag)) { - DBA::insert('tag', $fields, true); - $tagid = DBA::lastInsertId(); + if (($type != self::HASHTAG) && !empty($url) && ($url != $name)) { + $url = strtolower($url); } else { - $tagid = $tag['id']; + $url = ''; } + $tagid = self::getID($name, $url); if (empty($tagid)) { - Logger::error('No tag id created', $fields); return; } } $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; } } - DBA::insert('post-tag', $fields, true); + DBA::insert('post-tag', $fields, Database::INSERT_IGNORE); Logger::info('Stored tag/mention', ['uri-id' => $uriid, 'tag-id' => $tagid, 'contact-id' => $cid, 'name' => $name, 'type' => $type, 'callstack' => System::callstack(8)]); } + /** + * Get a tag id for a given tag name and url + * + * @param string $name + * @param string $url + * @return void + */ + public static function getID(string $name, string $url = '') + { + $fields = ['name' => substr($name, 0, 96), 'url' => $url]; + + $tag = DBA::selectFirst('tag', ['id'], $fields); + if (DBA::isResult($tag)) { + return $tag['id']; + } + + DBA::insert('tag', $fields, Database::INSERT_IGNORE); + $tid = DBA::lastInsertId(); + if (!empty($tid)) { + return $tid; + } + + Logger::error('No tag id created', $fields); + return 0; + } + /** * Store tag/mention elements * @@ -296,6 +330,29 @@ class Tag } } + /** + * Create implicit mentions for a given post + * + * @param integer $uri_id + * @param integer $parent_uri_id + */ + public static function createImplicitMentions(int $uri_id, int $parent_uri_id) + { + // Always mention the direct parent author + $parent = Item::selectFirst(['author-link', 'author-name'], ['uri-id' => $parent_uri_id]); + self::store($uri_id, self::IMPLICIT_MENTION, $parent['author-name'], $parent['author-link']); + + if (DI::config()->get('system', 'disable_implicit_mentions')) { + return; + } + + $tags = DBA::select('tag-view', ['name', 'url'], ['uri-id' => $parent_uri_id]); + while ($tag = DBA::fetch($tags)) { + self::store($uri_id, self::IMPLICIT_MENTION, $tag['name'], $tag['url']); + } + DBA::close($tags); + } + /** * Retrieves the terms from the provided type(s) associated with the provided item ID. * @@ -304,24 +361,32 @@ class Tag * @return array * @throws \Exception */ - public static function ArrayFromURIId(int $uri_id, array $type = [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION, self::EXCLUSIVE_MENTION]) + 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]; - $tags = DBA::select('tag-view', ['type', 'name', 'url'], $condition); - if (!DBA::isResult($tags)) { - return []; - } + return DBA::selectToArray('tag-view', ['type', 'name', 'url'], $condition); + } + /** + * Return a string with all tags and mentions + * + * @param integer $uri_id + * @param array $type + * @return string tags and mentions + * @throws \Exception + */ + public static function getCSVByURIId(int $uri_id, array $type = [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION, self::EXCLUSIVE_MENTION]) + { $tag_list = []; - while ($tag = DBA::fetch($tags)) { - $tag['term'] = $tag['name']; /// @todo Remove this line when all occurrences of "term" had been replaced with "name" - $tag_list[] = $tag; + $tags = self::getByURIId($uri_id, $type); + foreach ($tags as $tag) { + $tag_list[] = self::TAG_CHARACTER[$tag['type']] . '[url=' . $tag['url'] . ']' . $tag['name'] . '[/url]'; } - return $tag_list; + return implode(',', $tag_list); } - /** + /** * 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. * @@ -330,7 +395,7 @@ class Tag * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \ImagickException */ - public static function populateTagsFromItem(&$item) + public static function populateFromItem(&$item) { $return = [ 'tags' => [], @@ -376,4 +441,194 @@ class Tag return $return; } + /** + * Counts posts for given tag + * + * @param string $search + * @param integer $uid + * @return integer number of posts + */ + public static function countByTag(string $search, int $uid = 0) + { + $condition = ["`name` = ? AND (NOT `private` OR (`private` AND `uid` = ?))", $search, $uid]; + $params = ['group_by' => ['uri-id']]; + + return DBA::count('tag-search-view', $condition, $params); + } + + /** + * Search posts for given tag + * + * @param string $search + * @param integer $uid + * @param integer $start + * @param integer $limit + * @param integer $last_uriid + * @return array with URI-ID + */ + public static function getURIIdListByTag(string $search, int $uid = 0, int $start = 0, int $limit = 100, int $last_uriid = 0) + { + $condition = ["`name` = ? AND (NOT `private` OR (`private` AND `uid` = ?))", $search, $uid]; + + if (!empty($last_uriid)) { + $condition = DBA::mergeConditions($condition, ["`uri-id` < ?", $last_uriid]); + } + + $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 + * @param int $limit Number of returned tags + * @return array + * @throws \Exception + */ + public static function getGlobalTrendingHashtags(int $period, $limit = 10) + { + $tags = DI::cache()->get('global_trending_tags-' . $period . '-' . $limit); + if (!empty($tags)) { + return $tags; + } else { + return self::setGlobalTrendingHashtags($period, $limit); + } + } + + /** + * Creates a list of the most frequent global hashtags over the given period + * + * @param int $period Period in hours to consider posts + * @param int $limit Number of returned tags + * @return array + * @throws \Exception + */ + public static function setGlobalTrendingHashtags(int $period, int $limit = 10) + { + $tagsStmt = DBA::p("SELECT `name` AS `term`, COUNT(*) AS `score` + FROM `tag-search-view` + WHERE `private` = ? AND `uid` = ? AND `received` > DATE_SUB(NOW(), INTERVAL ? HOUR) + GROUP BY `term` ORDER BY `score` DESC LIMIT ?", + Item::PUBLIC, 0, $period, $limit); + + if (DBA::isResult($tagsStmt)) { + $tags = DBA::toArray($tagsStmt); + DI::cache()->set('global_trending_tags-' . $period . '-' . $limit, $tags, Duration::DAY); + return $tags; + } + + return []; + } + + /** + * Returns a list of the most frequent local hashtags over the given period + * + * @param int $period Period in hours to consider posts + * @param int $limit Number of returned tags + * @return array + * @throws \Exception + */ + public static function getLocalTrendingHashtags(int $period, $limit = 10) + { + $tags = DI::cache()->get('local_trending_tags-' . $period . '-' . $limit); + if (!empty($tags)) { + return $tags; + } else { + return self::setLocalTrendingHashtags($period, $limit); + } + } + + /** + * Returns a list of the most frequent local hashtags over the given period + * + * @param int $period Period in hours to consider posts + * @param int $limit Number of returned tags + * @return array + * @throws \Exception + */ + public static function setLocalTrendingHashtags(int $period, int $limit = 10) + { + $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-' . $period . '-' . $limit, $tags, Duration::DAY); + return $tags; + } + + return []; + } + + /** + * 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::startsWithChars($tag, $tag_chars); + } + + /** + * Fetch user who subscribed to the given tag + * + * @param string $tag + * @return array User list + */ + private static function getUIDListByTag(string $tag) + { + $uids = []; + $searches = DBA::select('search', ['uid'], ['term' => $tag]); + while ($search = DBA::fetch($searches)) { + $uids[] = $search['uid']; + } + DBA::close($searches); + + return $uids; + } + + /** + * Fetch user who subscribed to the tags of the given item + * + * @param integer $uri_id + * @return array User list + */ + public static function getUIDListByURIId(int $uri_id) + { + $uids = []; + $tags = self::getByURIId($uri_id, [self::HASHTAG]); + + foreach ($tags as $tag) { + $uids = array_merge($uids, self::getUIDListByTag(self::TAG_CHARACTER[self::HASHTAG] . $tag['name'])); + } + + return array_unique($uids); + } }