1
0
Fork 0

Merge remote-tracking branch 'upstream/develop' into post-delivery-data

This commit is contained in:
Michael 2020-05-04 14:40:52 +00:00
commit 16d486dd24
15 changed files with 62 additions and 544 deletions

View file

@ -80,7 +80,7 @@ class Item
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',
'attach', 'deleted', 'extid', 'post-type',
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid',
'author-id', 'author-link', 'owner-link', 'contact-uid',
'signed_text', 'signature', 'signer', 'network'];
@ -98,7 +98,7 @@ class Item
'guid', 'uri-id', 'parent-uri-id', 'thr-parent-id',
'contact-id', 'type', 'wall', 'gravity', 'extid', 'icid', 'iaid', 'psid',
'created', 'edited', 'commented', 'received', 'changed', 'verb',
'postopts', 'plink', 'resource-id', 'event-id', 'tag', 'attach', 'inform',
'postopts', 'plink', 'resource-id', 'event-id', 'attach', 'inform',
'file', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'post-type',
'private', 'pubmail', 'moderated', 'visible', 'starred', 'bookmark',
'unseen', 'deleted', 'origin', 'forum_mode', 'mention', 'global', 'network',
@ -317,11 +317,6 @@ class Item
}
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']);
}
// Build the file string out of the term entries
if (array_key_exists('file', $row) && empty($row['file'])) {
$row['file'] = Term::fileTextFromItemId($row['internal-iid']);
@ -673,7 +668,7 @@ class Item
'guid', 'uri-id', 'parent-uri-id', 'thr-parent-id',
'contact-id', 'owner-id', 'author-id', 'type', 'wall', 'gravity', 'extid',
'created', 'edited', 'commented', 'received', 'changed', 'psid',
'resource-id', 'event-id', 'tag', 'attach', 'post-type', 'file',
'resource-id', 'event-id', 'attach', 'post-type', 'file',
'private', 'pubmail', 'moderated', 'visible', 'starred', 'bookmark',
'unseen', 'deleted', 'origin', 'forum_mode', 'mention', 'global',
'id' => 'item_id', 'network', 'icid', 'iaid', 'id' => 'internal-iid',
@ -922,7 +917,7 @@ class Item
// We cannot simply expand the condition to check for origin entries
// The condition needn't to be a simple array but could be a complex condition.
// And we have to execute this query before the update to ensure to fetch the same data.
$items = DBA::select('item', ['id', 'origin', 'uri', 'uri-id', 'iaid', 'icid', 'tag', 'file'], $condition);
$items = DBA::select('item', ['id', 'origin', 'uri', 'uri-id', 'iaid', 'icid', 'file'], $condition);
$content_fields = [];
foreach (array_merge(self::CONTENT_FIELDLIST, self::MIXED_CONTENT_FIELDLIST) as $field) {
@ -945,13 +940,6 @@ class Item
}
}
if (array_key_exists('tag', $fields)) {
$tags = $fields['tag'];
$fields['tag'] = null;
} else {
$tags = null;
}
if (array_key_exists('file', $fields)) {
$files = $fields['file'];
$fields['file'] = null;
@ -1024,13 +1012,6 @@ class Item
}
}
if (!is_null($tags)) {
Term::insertFromTagFieldByItemId($item['id'], $tags);
if (!empty($item['tag'])) {
DBA::update('item', ['tag' => ''], ['id' => $item['id']]);
}
}
if (!is_null($files)) {
Term::insertFromFileFieldByItemId($item['id'], $files);
if (!empty($item['file'])) {
@ -1184,9 +1165,6 @@ class Item
}
}
// Delete tags that had been attached to other items
self::deleteTagsFromItem($item);
// Delete notifications
DBA::delete('notify', ['iid' => $item['id'], 'uid' => $item['uid']]);
@ -1194,7 +1172,6 @@ class Item
$item_fields = ['deleted' => true, 'edited' => DateTimeFormat::utcNow(), 'changed' => DateTimeFormat::utcNow()];
DBA::update('item', $item_fields, ['id' => $item['id']]);
Term::insertFromTagFieldByItemId($item['id'], '');
Term::insertFromFileFieldByItemId($item['id'], '');
self::deleteThread($item['id'], $item['parent-uri']);
@ -1243,43 +1220,6 @@ class Item
return true;
}
private static function deleteTagsFromItem($item)
{
if (($item["verb"] != Activity::TAG) || ($item["object-type"] != Activity\ObjectType::TAGTERM)) {
return;
}
$xo = XML::parseString($item["object"]);
$xt = XML::parseString($item["target"]);
if ($xt->type != Activity\ObjectType::NOTE) {
return;
}
$i = self::selectFirst(['id', 'contact-id', 'tag'], ['uri' => $xt->id, 'uid' => $item['uid']]);
if (!DBA::isResult($i)) {
return;
}
// For tags, the owner cannot remove the tag on the author's copy of the post.
$owner_remove = ($item["contact-id"] == $i["contact-id"]);
$author_copy = $item["origin"];
if (($owner_remove && $author_copy) || !$owner_remove) {
return;
}
$tags = explode(',', $i["tag"]);
$newtags = [];
if (count($tags)) {
foreach ($tags as $tag) {
if (trim($tag) !== trim($xo->body)) {
$newtags[] = trim($tag);
}
}
}
self::update(['tag' => implode(',', $newtags)], ['id' => $i["id"]]);
}
private static function guid($item, $notify)
{
@ -1547,7 +1487,6 @@ class Item
$item['deny_gid'] = trim($item['deny_gid'] ?? '');
$item['private'] = intval($item['private'] ?? self::PUBLIC);
$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);
@ -1870,13 +1809,6 @@ class Item
Logger::log('' . print_r($item,true), Logger::DATA);
if (array_key_exists('tag', $item)) {
$tags = $item['tag'];
unset($item['tag']);
} else {
$tags = '';
}
if (array_key_exists('file', $item)) {
$files = $item['file'];
unset($item['file']);
@ -2016,10 +1948,6 @@ class Item
* Due to deadlock issues with the "term" table we are doing these steps after the commit.
* This is not perfect - but a workable solution until we found the reason for the problem.
*/
if (!empty($tags)) {
Term::insertFromTagFieldByItemId($current_post, $tags);
}
if (!empty($files)) {
Term::insertFromFileFieldByItemId($current_post, $files);
}
@ -2634,9 +2562,6 @@ class Item
if (DI::config()->get('system', 'local_tags')) {
$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
"#[url=".DI::baseUrl()."/search?tag=$2]$2[/url]", $item["body"]);
$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
"#[url=".DI::baseUrl()."/search?tag=$2]$2[/url]", $item["tag"]);
}
// mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
@ -2668,13 +2593,6 @@ class Item
$newtag = '#[url=' . DI::baseUrl() . '/search?tag=' . $basetag . ']' . $basetag . '[/url]';
$item["body"] = str_replace($tag, $newtag, $item["body"]);
if (!stristr($item["tag"], "/search?tag=" . $basetag . "]" . $basetag . "[/url]")) {
if (strlen($item["tag"])) {
$item["tag"] = ',' . $item["tag"];
}
$item["tag"] = $newtag . $item["tag"];
}
}
// Convert back the masked hashtags
@ -3032,30 +2950,6 @@ class Item
return $recipients;
}
public static function getFeedTags($item)
{
$ret = [];
$matches = false;
$cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|', $item['tag'], $matches);
if ($cnt) {
for ($x = 0; $x < $cnt; $x ++) {
if ($matches[1][$x]) {
$ret[$matches[2][$x]] = ['#', $matches[1][$x], $matches[2][$x]];
}
}
}
$matches = false;
$cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|', $item['tag'], $matches);
if ($cnt) {
for ($x = 0; $x < $cnt; $x ++) {
if ($matches[1][$x]) {
$ret[] = ['@', $matches[1][$x], $matches[2][$x]];
}
}
}
return $ret;
}
public static function expire($uid, $days, $network = "", $force = false)
{
if (!$uid || ($days < 1)) {

View file

@ -40,8 +40,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.
*/

View file

@ -21,10 +21,7 @@
namespace Friendica\Model;
use Friendica\Core\Logger;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Util\Strings;
/**
* Class Term
@ -36,66 +33,10 @@ use Friendica\Util\Strings;
class Term
{
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.
*/
const IMPLICIT_MENTION = 8;
/**
* An exclusive mention transfers the ownership of the post to the target account, usually a forum.
*/
const EXCLUSIVE_MENTION = 9;
const TAG_CHARACTER = [
self::HASHTAG => '#',
self::MENTION => '@',
self::IMPLICIT_MENTION => '%',
self::EXCLUSIVE_MENTION => '!',
];
const OBJECT_TYPE_POST = 1;
const OBJECT_TYPE_PHOTO = 2;
/**
* Generates the legacy item.tag field comma-separated BBCode string from an item ID.
* Includes only hashtags, implicit and explicit mentions.
*
* @param int $item_id
* @return string
* @throws \Exception
*/
public static function tagTextFromItemId($item_id)
{
$tag_list = [];
$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]';
}
return implode(',', $tag_list);
}
/**
* 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
*/
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);
if (!DBA::isResult($tags)) {
return [];
}
return DBA::toArray($tags);
}
/**
* Generates the legacy item.file field string from an item ID.
@ -108,7 +49,9 @@ class Term
public static function fileTextFromItemId($item_id)
{
$file_text = '';
$tags = self::getByItemId($item_id, [self::FILE, self::CATEGORY]);
$condition = ['otype' => self::OBJECT_TYPE_POST, 'oid' => $item_id, 'type' => [self::FILE, self::CATEGORY]];
$tags = DBA::selectToArray('term', ['type', 'term', 'url'], $condition);
foreach ($tags as $tag) {
if ($tag['type'] == self::CATEGORY) {
$file_text .= '<' . $tag['term'] . '>';
@ -120,170 +63,6 @@ class Term
return $file_text;
}
/**
* Inserts new terms for the provided item ID based on the legacy item.tag field BBCode content.
* Deletes all previous tag terms for the same item ID.
* Sets both the item.mention and thread.mentions field flags if a mention concerning the item UID is found.
*
* @param int $item_id
* @param string $tag_str
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function insertFromTagFieldByItemId($item_id, $tag_str)
{
$profile_base = DI::baseUrl();
$profile_data = parse_url($profile_base);
$profile_path = $profile_data['path'] ?? '';
$profile_base_friendica = $profile_data['host'] . $profile_path . '/profile/';
$profile_base_diaspora = $profile_data['host'] . $profile_path . '/u/';
$fields = ['guid', 'uid', 'id', 'edited', 'deleted', 'created', 'received', 'title', 'body', 'parent'];
$item = Item::selectFirst($fields, ['id' => $item_id]);
if (!DBA::isResult($item)) {
return;
}
$item['tag'] = $tag_str;
// Clean up all tags
self::deleteByItemId($item_id);
if ($item['deleted']) {
return;
}
$taglist = explode(',', $item['tag']);
$tags_string = '';
foreach ($taglist as $tag) {
if (Strings::startsWith($tag, self::TAG_CHARACTER)) {
$tags_string .= ' ' . trim($tag);
} else {
$tags_string .= ' #' . trim($tag);
}
}
$data = ' ' . $item['title'] . ' ' . $item['body'] . ' ' . $tags_string . ' ';
// ignore anything in a code block
$data = preg_replace('/\[code\](.*?)\[\/code\]/sm', '', $data);
$tags = [];
$pattern = '/\W\#([^\[].*?)[\s\'".,:;\?!\[\]\/]/ism';
if (preg_match_all($pattern, $data, $matches)) {
foreach ($matches[1] as $match) {
$tags['#' . $match] = '';
}
}
$pattern = '/\W([\#@!%])\[url\=(.*?)\](.*?)\[\/url\]/ism';
if (preg_match_all($pattern, $data, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
if (in_array($match[1], [
self::TAG_CHARACTER[self::MENTION],
self::TAG_CHARACTER[self::IMPLICIT_MENTION],
self::TAG_CHARACTER[self::EXCLUSIVE_MENTION]
])) {
$contact = Contact::getDetailsByURL($match[2], 0);
if (!empty($contact['addr'])) {
$match[3] = $contact['addr'];
}
if (!empty($contact['url'])) {
$match[2] = $contact['url'];
}
}
$tags[$match[2]] = $match[1] . trim($match[3], ',.:;[]/\"?!');
}
}
foreach ($tags as $link => $tag) {
if (Tag::isType($tag, self::HASHTAG)) {
// try to ignore #039 or #1 or anything like that
if (ctype_digit(substr(trim($tag), 1))) {
continue;
}
// try to ignore html hex escapes, e.g. #x2317
if ((substr(trim($tag), 1, 1) == 'x' || substr(trim($tag), 1, 1) == 'X') && ctype_digit(substr(trim($tag), 2))) {
continue;
}
$type = self::HASHTAG;
$term = substr($tag, 1);
$link = '';
} 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;
}
$contact = Contact::getDetailsByURL($link, 0);
if (!empty($contact['name'])) {
$term = $contact['name'];
} else {
$term = substr($tag, 1);
}
} else { // This shouldn't happen
$type = self::HASHTAG;
$term = $tag;
$link = '';
Logger::notice('Unknown term type', ['tag' => $tag]);
}
if (DBA::exists('term', ['uid' => $item['uid'], 'otype' => self::OBJECT_TYPE_POST, 'oid' => $item_id, 'term' => $term, 'type' => $type])) {
continue;
}
if (empty($term)) {
continue;
}
if ($item['uid'] == 0) {
$global = true;
DBA::update('term', ['global' => true], ['otype' => self::OBJECT_TYPE_POST, 'guid' => $item['guid']]);
} else {
$global = DBA::exists('term', ['uid' => 0, 'otype' => self::OBJECT_TYPE_POST, 'guid' => $item['guid']]);
}
DBA::insert('term', [
'uid' => $item['uid'],
'oid' => $item_id,
'otype' => self::OBJECT_TYPE_POST,
'type' => $type,
'term' => substr($term, 0, 255),
'url' => $link,
'guid' => $item['guid'],
'created' => $item['created'],
'received' => $item['received'],
'global' => $global
]);
// Search for mentions
if (Tag::isType($tag, self::MENTION, self::EXCLUSIVE_MENTION)
&& (
strpos($link, $profile_base_friendica) !== false
|| strpos($link, $profile_base_diaspora) !== false
)
) {
$users_stmt = DBA::p("SELECT `uid` FROM `contact` WHERE self AND (`url` = ? OR `nurl` = ?)", $link, $link);
$users = DBA::toArray($users_stmt);
foreach ($users AS $user) {
if ($user['uid'] == $item['uid']) {
/// @todo This function is called from Item::update - so we mustn't call that function here
DBA::update('item', ['mention' => true], ['id' => $item_id]);
DBA::update('thread', ['mention' => true], ['iid' => $item['parent']]);
}
}
}
}
}
/**
* Inserts new terms for the provided item ID based on the legacy item.file field BBCode content.
* Deletes all previous file terms for the same item ID.
@ -333,21 +112,4 @@ class Term
}
}
}
/**
* Delete tags of the specific type(s) from an item
*
* @param int $item_id
* @param int|array $type
* @throws \Exception
*/
public static function deleteByItemId($item_id, $type = [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION])
{
if (empty($item_id)) {
return;
}
// Clean up all tags
DBA::delete('term', ['otype' => self::OBJECT_TYPE_POST, 'oid' => $item_id, 'type' => $type]);
}
}

View file

@ -227,9 +227,10 @@ class UserItem
*/
private static function checkImplicitMention(array $item, array $profiles)
{
foreach ($profiles AS $profile) {
if (strpos($item['tag'], '=' . $profile.']') || strpos($item['body'], '=' . $profile . ']')) {
if (strpos($item['body'], $profile) === false) {
$mentions = Tag::getByURIId($item['uri-id'], [Tag::IMPLICIT_MENTION]);
foreach ($mentions as $mention) {
foreach ($profiles as $profile) {
if (Strings::compareLink($profile, $mention['url'])) {
return true;
}
}
@ -246,9 +247,10 @@ class UserItem
*/
private static function checkExplicitMention(array $item, array $profiles)
{
foreach ($profiles AS $profile) {
if (strpos($item['tag'], '=' . $profile.']') || strpos($item['body'], '=' . $profile . ']')) {
if (!(strpos($item['body'], $profile) === false)) {
$mentions = Tag::getByURIId($item['uri-id'], [Tag::MENTION, Tag::EXCLUSIVE_MENTION]);
foreach ($mentions as $mention) {
foreach ($profiles as $profile) {
if (Strings::compareLink($profile, $mention['url'])) {
return true;
}
}