use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
+use Friendica\Model\Term;
use Friendica\Util\BasePath;
use Friendica\Util\DateTimeFormat;
/* @}*/
-/**
- * @name Term
- *
- * Tag/term types
- * @{
- */
-define('TERM_UNKNOWN', 0);
-define('TERM_HASHTAG', 1);
-define('TERM_MENTION', 2);
-define('TERM_CATEGORY', 3);
-define('TERM_PCATEGORY', 4);
-define('TERM_FILE', 5);
-define('TERM_SAVEDSEARCH', 6);
-define('TERM_CONVERSATION', 7);
-
-define('TERM_OBJ_POST', 1);
-define('TERM_OBJ_PHOTO', 2);
+/** @deprecated since 2019.03, use Term::UNKNOWN instead */
+define('TERM_UNKNOWN', Term::UNKNOWN);
+/** @deprecated since 2019.03, use Term::HASHTAG instead */
+define('TERM_HASHTAG', Term::HASHTAG);
+/** @deprecated since 2019.03, use Term::MENTION instead */
+define('TERM_MENTION', Term::MENTION);
+/** @deprecated since 2019.03, use Term::CATEGORY instead */
+define('TERM_CATEGORY', Term::CATEGORY);
+/** @deprecated since 2019.03, use Term::PCATEGORY instead */
+define('TERM_PCATEGORY', Term::PCATEGORY);
+/** @deprecated since 2019.03, use Term::FILE instead */
+define('TERM_FILE', Term::FILE);
+/** @deprecated since 2019.03, use Term::SAVEDSEARCH instead */
+define('TERM_SAVEDSEARCH', Term::SAVEDSEARCH);
+/** @deprecated since 2019.03, use Term::CONVERSATION instead */
+define('TERM_CONVERSATION', Term::CONVERSATION);
+
+/** @deprecated since 2019.03, use Term::OBJECT_TYPE_POST instead */
+define('TERM_OBJ_POST', Term::OBJECT_TYPE_POST);
+/** @deprecated since 2019.03, use Term::OBJECT_TYPE_PHOTO instead */
+define('TERM_OBJ_PHOTO', Term::OBJECT_TYPE_PHOTO);
/**
* @name Namespaces
// Disables the check if a mail address is in a valid format and can be resolved via DNS.
'disable_email_validation' => false,
- // disable_mentions_removal (Boolean)
- // Disables the automatic removal of implicit mentions in ActivityPub postings.
- 'disable_mentions_removal' => false,
+ // disable_implicit_mentions (Boolean) since 2019.03
+ // Implicit mentions are mentions in the body of replies that are redundant in a thread-enabled system like Friendica.
+ // This config key disables the gathering of implicit mentions in incoming and outgoing posts.
+ // Also disables the default automatic removal of implicit mentions from the body of incoming posts.
+ // Also disables the default automatic addition of implicit mentions in the body of outgoing posts.
+ // Disabling implicit mentions also affects the "explicit_mentions" additional feature by limiting it
+ // to the replied-to post author mention in the comment boxes.
+ 'disable_implicit_mentions' => false,
// disable_url_validation (Boolean)
// Disables the DNS lookup of an URL.
// Only create a redirection to a magic link when logged in
if (!empty($item['plink']) && (local_user() || remote_user())) {
- $item['plink'] = Contact::magicLinkbyContact($author, $item['plink']);
+ $item['plink'] = Contact::magicLinkByContact($author, $item['plink']);
}
}
$author = ['uid' => 0, 'id' => $item['author-id'],
'network' => $item['author-network'], 'url' => $item['author-link']];
- $profile_link = Contact::magicLinkbyContact($author);
+ $profile_link = Contact::magicLinkByContact($author);
if (strpos($profile_link, 'redir/') === 0) {
$sparkle = ' sparkle';
list($categories, $folders) = get_cats_and_terms($item);
- $profile_name_e = $profile_name;
-
if (!empty($item['content-warning']) && PConfig::get(local_user(), 'system', 'disable_cw', false)) {
- $title_e = ucfirst($item['content-warning']);
+ $title = ucfirst($item['content-warning']);
} else {
- $title_e = $item['title'];
+ $title = $item['title'];
}
- $body_e = $body;
- $tags_e = $tags['tags'];
- $hashtags_e = $tags['hashtags'];
- $mentions_e = $tags['mentions'];
- $location_e = $location;
- $owner_name_e = $owner_name;
-
$tmp_item = [
'template' => $tpl,
'id' => ($preview ? 'P0' : $item['id']),
'linktitle' => L10n::t('View %s\'s profile @ %s', $profile_name, $item['author-link']),
'profile_url' => $profile_link,
'item_photo_menu' => item_photo_menu($item),
- 'name' => $profile_name_e,
+ 'name' => $profile_name,
'sparkle' => $sparkle,
'lock' => $lock,
'thumb' => System::removedBaseUrl(ProxyUtils::proxifyUrl($item['author-avatar'], false, ProxyUtils::SIZE_THUMB)),
- 'title' => $title_e,
- 'body' => $body_e,
- 'tags' => $tags_e,
- 'hashtags' => $hashtags_e,
- 'mentions' => $mentions_e,
+ 'title' => $title,
+ 'body' => $body,
+ 'tags' => $tags['tags'],
+ 'hashtags' => $tags['hashtags'],
+ 'mentions' => $tags['mentions'],
+ 'implicit_mentions' => $tags['implicit_mentions'],
'txt_cats' => L10n::t('Categories:'),
'txt_folders' => L10n::t('Filed under:'),
'has_cats' => ((count($categories)) ? 'true' : ''),
'has_folders' => ((count($folders)) ? 'true' : ''),
'categories' => $categories,
'folders' => $folders,
- 'text' => strip_tags($body_e),
+ 'text' => strip_tags($body),
'localtime' => DateTimeFormat::local($item['created'], 'r'),
'ago' => (($item['app']) ? L10n::t('%s from %s', Temporal::getRelativeDate($item['created']),$item['app']) : Temporal::getRelativeDate($item['created'])),
- 'location' => $location_e,
+ 'location' => $location,
'indent' => '',
- 'owner_name' => $owner_name_e,
+ 'owner_name' => $owner_name,
'owner_url' => $owner_url,
'owner_photo' => System::removedBaseUrl(ProxyUtils::proxifyUrl($item['owner-avatar'], false, ProxyUtils::SIZE_THUMB)),
'plink' => Item::getPlink($item),
$author = ['uid' => 0, 'id' => $item['author-id'],
'network' => $item['author-network'], 'url' => $item['author-link']];
- $profile_link = Contact::magicLinkbyContact($author);
+ $profile_link = Contact::magicLinkByContact($author);
$sparkle = (strpos($profile_link, 'redir/') === 0);
$cid = 0;
if (activity_match($item['verb'], $verb) && ($item['id'] != $item['parent'])) {
$author = ['uid' => 0, 'id' => $item['author-id'],
'network' => $item['author-network'], 'url' => $item['author-link']];
- $url = Contact::magicLinkbyContact($author);
+ $url = Contact::magicLinkByContact($author);
if (strpos($url, 'redir/') === 0) {
$sparkle = ' class="sparkle" ';
}
$a = \get_app();
// Temporary logging for finding the origin
- if (!isset($params['language']) || !isset($params['uid'])) {
- Logger::log('Missing parameters.' . System::callstack());
+ if (!isset($params['uid'])) {
+ Logger::notice('Missing parameters "uid".', ['params' => $params, 'callstack' => System::callstack()]);
}
// Ensure that the important fields are set at any time
$user = DBA::selectFirst('user', $fields, ['uid' => $params['uid']]);
if (!DBA::isResult($user)) {
- Logger::log('Unknown user ' . $params['uid']);
+ Logger::error('Unknown user', ['uid' => $params['uid']]);
return false;
}
use Friendica\Model\Item;
use Friendica\Model\Photo;
use Friendica\Model\Attach;
+use Friendica\Model\Term;
use Friendica\Protocol\Diaspora;
use Friendica\Protocol\Email;
use Friendica\Util\DateTimeFormat;
}
// Is this a reply to something?
- $thr_parent = intval(defaults($_REQUEST, 'parent', 0));
+ $toplevel_item_id = intval(defaults($_REQUEST, 'parent', 0));
$thr_parent_uri = trim(defaults($_REQUEST, 'parent_uri', ''));
- $thr_parent_contact = null;
+ $thread_parent_id = 0;
+ $thread_parent_contact = null;
- $parent = 0;
- $parent_item = null;
+ $toplevel_item = null;
$parent_user = null;
$parent_contact = null;
$profile_uid = defaults($_REQUEST, 'profile_uid', local_user());
$posttype = defaults($_REQUEST, 'post_type', Item::PT_ARTICLE);
- if ($thr_parent || $thr_parent_uri) {
- if ($thr_parent) {
- $parent_item = Item::selectFirst([], ['id' => $thr_parent]);
+ if ($toplevel_item_id || $thr_parent_uri) {
+ if ($toplevel_item_id) {
+ $toplevel_item = Item::selectFirst([], ['id' => $toplevel_item_id]);
} elseif ($thr_parent_uri) {
- $parent_item = Item::selectFirst([], ['uri' => $thr_parent_uri, 'uid' => $profile_uid]);
+ $toplevel_item = Item::selectFirst([], ['uri' => $thr_parent_uri, 'uid' => $profile_uid]);
}
- // if this isn't the real parent of the conversation, find it
- if (DBA::isResult($parent_item)) {
+ // 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
- $thr_parent_uri = $parent_item['uri'];
- $thr_parent_contact = Contact::getDetailsByURL($parent_item["author-link"]);
+ $thread_parent_id = $toplevel_item['id'];
+ $thr_parent_uri = $toplevel_item['uri'];
+ $thread_parent_contact = Contact::getDetailsByURL($toplevel_item["author-link"]);
- if ($parent_item['id'] != $parent_item['parent']) {
- $parent_item = Item::selectFirst(Item::ITEM_FIELDLIST, ['id' => $parent_item['parent']]);
+ if ($toplevel_item['id'] != $toplevel_item['parent']) {
+ $toplevel_item = Item::selectFirst(Item::ITEM_FIELDLIST, ['id' => $toplevel_item['parent']]);
}
}
- if (!DBA::isResult($parent_item)) {
+ if (!DBA::isResult($toplevel_item)) {
notice(L10n::t('Unable to locate original post.') . EOL);
if (!empty($_REQUEST['return'])) {
$a->internalRedirect($return_path);
exit();
}
- $parent = $parent_item['id'];
- $parent_user = $parent_item['uid'];
+ $toplevel_item_id = $toplevel_item['id'];
+ $parent_user = $toplevel_item['uid'];
$objecttype = ACTIVITY_OBJ_COMMENT;
}
- if ($parent) {
- Logger::log('mod_item: item_post parent=' . $parent);
+ if ($toplevel_item_id) {
+ Logger::info('mod_item: item_post parent=' . $toplevel_item_id);
}
$post_id = intval(defaults($_REQUEST, 'post_id', 0));
}
// Allow commenting if it is an answer to a public post
- $allow_comment = local_user() && ($profile_uid == 0) && $parent && in_array($parent_item['network'], [Protocol::ACTIVITYPUB, Protocol::OSTATUS, Protocol::DIASPORA, Protocol::DFRN]);
+ $allow_comment = local_user() && ($profile_uid == 0) && $toplevel_item_id && in_array($toplevel_item['network'], [Protocol::ACTIVITYPUB, Protocol::OSTATUS, Protocol::DIASPORA, Protocol::DFRN]);
// Now check that valid personal details have been provided
if (!Security::canWriteToUserWall($profile_uid) && !$allow_comment) {
$user = DBA::selectFirst('user', [], ['uid' => $profile_uid]);
- if (!DBA::isResult($user) && !$parent) {
+ if (!DBA::isResult($user) && !$toplevel_item_id) {
return 0;
}
// If this is a comment, set the permissions from the parent.
- if ($parent_item) {
+ if ($toplevel_item) {
// for non native networks use the network of the original post as network of the item
- if (($parent_item['network'] != Protocol::DIASPORA)
- && ($parent_item['network'] != Protocol::OSTATUS)
+ if (($toplevel_item['network'] != Protocol::DIASPORA)
+ && ($toplevel_item['network'] != Protocol::OSTATUS)
&& ($network == "")) {
- $network = $parent_item['network'];
+ $network = $toplevel_item['network'];
}
- $str_contact_allow = $parent_item['allow_cid'];
- $str_group_allow = $parent_item['allow_gid'];
- $str_contact_deny = $parent_item['deny_cid'];
- $str_group_deny = $parent_item['deny_gid'];
- $private = $parent_item['private'];
+ $str_contact_allow = $toplevel_item['allow_cid'];
+ $str_group_allow = $toplevel_item['allow_gid'];
+ $str_contact_deny = $toplevel_item['deny_cid'];
+ $str_group_deny = $toplevel_item['deny_gid'];
+ $private = $toplevel_item['private'];
- $wall = $parent_item['wall'];
+ $wall = $toplevel_item['wall'];
}
$pubmail_enabled = defaults($_REQUEST, 'pubmail_enable', false) && !$private;
$tags = BBCode::getTags($body);
- // Add a tag if the parent contact is from ActivityPub or OStatus (This will notify them)
- if ($parent && in_array($thr_parent_contact['network'], [Protocol::OSTATUS, Protocol::ACTIVITYPUB])) {
- $contact = '@[url=' . $thr_parent_contact['url'] . ']' . $thr_parent_contact['nick'] . '[/url]';
- if (!stripos(implode($tags), '[url=' . $thr_parent_contact['url'] . ']')) {
- $tags[] = $contact;
- }
+ if ($thread_parent_id && !\Friendica\Content\Feature::isEnabled($uid, 'explicit_mentions')) {
+ $tags = item_add_implicit_mentions($tags, $thread_parent_contact, $thread_parent_id);
}
$tagged = [];
foreach ($tags as $tag) {
$tag_type = substr($tag, 0, 1);
- if ($tag_type == '#') {
+ if ($tag_type == Term::TAG_CHARACTER[Term::HASHTAG]) {
continue;
}
$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 == '!'))) {
+ if (is_array($success['contact']) && (!empty($success['contact']['prv']) || ($tag_type == Term::TAG_CHARACTER[Term::EXCLUSIVE_MENTION]))) {
$private_forum = $success['contact']['prv'];
- $only_to_forum = ($tag_type == '!');
+ $only_to_forum = ($tag_type == Term::TAG_CHARACTER[Term::EXCLUSIVE_MENTION]);
$private_id = $success['contact']['id'];
$forum_contact = $success['contact'];
} elseif (is_array($success['contact']) && !empty($success['contact']['forum']) &&
$original_contact_id = $contact_id;
- if (!$parent && count($forum_contact) && ($private_forum || $only_to_forum)) {
+ if (!$toplevel_item_id && count($forum_contact) && ($private_forum || $only_to_forum)) {
// we tagged a forum in a top level post. Now we change the post
$private = $private_forum;
$network = Protocol::DFRN;
}
- $gravity = ($parent ? GRAVITY_COMMENT : GRAVITY_PARENT);
+ $gravity = ($toplevel_item_id ? GRAVITY_COMMENT : GRAVITY_PARENT);
// even if the post arrived via API we are considering that it
// originated on this site by default for determining relayability.
$origin = $_REQUEST['origin'];
}
- $notify_type = ($parent ? 'comment-new' : 'wall-new');
+ $notify_type = ($toplevel_item_id ? 'comment-new' : 'wall-new');
$uri = ($message_id ? $message_id : Item::newURI($api_source ? $profile_uid : $uid, $guid));
// Fallback so that we alway have a parent uri
- if (!$thr_parent_uri || !$parent) {
+ if (!$thr_parent_uri || !$toplevel_item_id) {
$thr_parent_uri = $uri;
}
* 'self' if true indicates the owner is posting on their own wall
* If parent is 0 it is a top-level post.
*/
- $datarray['parent'] = $parent;
+ $datarray['parent'] = $toplevel_item_id;
$datarray['self'] = $self;
// This triggers posts via API and the mirror functions
FileTag::updatePconfig($uid, $categories_old, $categories_new, 'category');
// These notifications are sent if someone else is commenting other your wall
- if ($parent) {
+ if ($toplevel_item_id) {
if ($contact_record != $author) {
notification([
'type' => NOTIFY_COMMENT,
'source_photo' => $datarray['author-avatar'],
'verb' => ACTIVITY_POST,
'otype' => 'item',
- 'parent' => $parent,
- 'parent_uri' => $parent_item['uri']
+ 'parent' => $toplevel_item_id,
+ 'parent_uri' => $toplevel_item['uri']
]);
}
} else {
$r = null;
//is it a person tag?
- if ((strpos($tag, '@') === 0) || (strpos($tag, '!') === 0)) {
+ if (Term::isType($tag, Term::MENTION, Term::IMPLICIT_MENTION, Term::EXCLUSIVE_MENTION)) {
$tag_type = substr($tag, 0, 1);
//is it already replaced?
if (strpos($tag, '[url=')) {
return ['replaced' => $replaced, 'contact' => $contact];
}
+
+function item_add_implicit_mentions(array $tags, array $thread_parent_contact, $thread_parent_id)
+{
+ if (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]';
+ if (!stripos(implode($tags), '[url=' . $thread_parent_contact['url'] . ']')) {
+ $tags[] = $contact;
+ }
+ }
+ } else {
+ $implicit_mentions = [
+ $thread_parent_contact['url'] => $thread_parent_contact['nick']
+ ];
+
+ $parent_terms = Term::tagArrayFromItemId($thread_parent_id, [Term::MENTION, Term::IMPLICIT_MENTION]);
+
+ foreach ($parent_terms as $parent_term) {
+ $implicit_mentions[$parent_term['url']] = $parent_term['term'];
+ }
+
+ 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]';
+ }
+ }
+ }
+
+ return $tags;
+}
if ((substr_count($sql, '?') != count($args)) && (count($args) > 0)) {
// Question: Should we continue or stop the query here?
- Logger::log('Parameter mismatch. Query "'.$sql.'" - Parameters '.print_r($args, true), Logger::DEBUG);
+ Logger::warning('Query parameters mismatch.', ['query' => $sql, 'args' => $args, 'callstack' => System::callstack()]);
}
$sql = self::cleanQuery($sql);
{
$contact = DBA::selectFirst('contact', ['id', 'network', 'url', 'uid'], ['id' => $cid]);
- return self::magicLinkbyContact($contact, $url);
+ return self::magicLinkByContact($contact, $url);
}
/**
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
- public static function magicLinkbyContact($contact, $url = '')
+ public static function magicLinkByContact($contact, $url = '')
{
if ((!local_user() && !remote_user()) || ($contact['network'] != Protocol::DFRN)) {
return $url ?: $contact['url']; // Equivalent to ($url != '') ? $url : $contact['url'];
$expire_date = time() - ($expire_interval * 86400);
$created_date = strtotime($item['created']);
if ($created_date < $expire_date) {
- Logger::log('item-store: item created ('.date('c', $created_date).') before expiration time ('.date('c', $expire_date).'). ignored. ' . print_r($item,true), Logger::DEBUG);
+ Logger::notice('Item created before expiration interval.', [
+ 'created' => date('c', $created_date),
+ 'expired' => date('c', $expire_date),
+ '$item' => $item
+ ]);
return 0;
}
}
if (DBA::isResult($existing)) {
// We only log the entries with a different user id than 0. Otherwise we would have too many false positives
if ($uid != 0) {
- Logger::log("Item with uri ".$item['uri']." already existed for user ".$uid." with id ".$existing["id"]." target network ".$existing["network"]." - new network: ".$item['network']);
+ Logger::notice('Item already existed for user', [
+ 'uri' => $item['uri'],
+ 'uid' => $uid,
+ 'network' => $item['network'],
+ 'existing_id' => $existing["id"],
+ 'existing_network' => $existing["network"]
+ ]);
}
return $existing["id"];
// When there is no content then we don't post it
if ($item['body'].$item['title'] == '') {
- Logger::log('No body, no title.');
+ Logger::notice('No body, no title.');
return 0;
}
$item['author-id'] = defaults($item, 'author-id', Contact::getIdForURL($item["author-link"], 0, false, $default));
if (Contact::isBlocked($item["author-id"])) {
- Logger::log('Contact '.$item["author-id"].' is blocked, item '.$item["uri"].' will not be stored');
+ Logger::notice('Author is blocked node-wide', ['author-link' => $item["author-link"], 'item-uri' => $item["uri"]]);
return 0;
}
$item['owner-id'] = defaults($item, 'owner-id', Contact::getIdForURL($item["owner-link"], 0, false, $default));
if (Contact::isBlocked($item["owner-id"])) {
- Logger::log('Contact '.$item["owner-id"].' is blocked, item '.$item["uri"].' will not be stored');
+ Logger::notice('Owner is blocked node-wide', ['owner-link' => $item["owner-link"], 'item-uri' => $item["uri"]]);
return 0;
}
if ($item['network'] == Protocol::PHANTOM) {
- Logger::log('Missing network. Called by: '.System::callstack(), Logger::DEBUG);
-
$item['network'] = Protocol::DFRN;
- Logger::log("Set network to " . $item["network"] . " for " . $item["uri"], Logger::DEBUG);
+ Logger::notice('Missing network, setting to {network}.', [
+ 'uri' => $item["uri"],
+ 'network' => $item['network'],
+ 'callstack' => System::callstack()
+ ]);
}
// Checking if there is already an item with the same guid
- Logger::log('Checking for an item for user '.$item['uid'].' on network '.$item['network'].' with the guid '.$item['guid'], Logger::DEBUG);
$condition = ['guid' => $item['guid'], 'network' => $item['network'], 'uid' => $item['uid']];
if (self::exists($condition)) {
- Logger::log('found item with guid '.$item['guid'].' for user '.$item['uid'].' on network '.$item['network'], Logger::DEBUG);
+ Logger::notice('Found already existing item', [
+ 'guid' => $item['guid'],
+ 'uid' => $item['uid'],
+ 'network' => $item['network']
+ ]);
return 0;
}
<?php
/**
- * @file src/Model/Term
+ * @file src/Model/Term.php
*/
namespace Friendica\Model;
use Friendica\Core\System;
use Friendica\Database\DBA;
+use Friendica\Util\Strings;
+/**
+ * Class Term
+ *
+ * This Model class handles term table interactions.
+ * This tables stores relevant terms related to posts, photos and searches, like hashtags, mentions and
+ * user-applied categories.
+ *
+ * @package Friendica\Model
+ */
class Term
{
- public static function tagTextFromItemId($itemid)
- {
- $tag_text = '';
- $condition = ['otype' => TERM_OBJ_POST, 'oid' => $itemid, 'type' => [TERM_HASHTAG, TERM_MENTION]];
- $tags = DBA::select('term', [], $condition);
- while ($tag = DBA::fetch($tags)) {
- if ($tag_text != '') {
- $tag_text .= ',';
- }
+ const UNKNOWN = 0;
+ const HASHTAG = 1;
+ const MENTION = 2;
+ const CATEGORY = 3;
+ const PCATEGORY = 4;
+ const FILE = 5;
+ const SAVEDSEARCH = 6;
+ const CONVERSATION = 7;
+ /**
+ * 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;
- if ($tag['type'] == 1) {
- $tag_text .= '#';
- } else {
- $tag_text .= '@';
- }
- $tag_text .= '[url=' . $tag['url'] . ']' . $tag['term'] . '[/url]';
+ 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::tagArrayFromItemId($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 $tag_text;
+
+ return implode(',', $tag_list);
}
- public static function tagArrayFromItemId($itemid, $type = [TERM_HASHTAG, TERM_MENTION])
+ /**
+ * 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 tagArrayFromItemId($item_id, $type = [self::HASHTAG, self::MENTION])
{
- $condition = ['otype' => TERM_OBJ_POST, 'oid' => $itemid, 'type' => $type];
+ $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);
}
- public static function fileTextFromItemId($itemid)
+ /**
+ * Generates the legacy item.file field string from an item ID.
+ * Includes only file and category terms.
+ *
+ * @param int $item_id
+ * @return string
+ * @throws \Exception
+ */
+ public static function fileTextFromItemId($item_id)
{
$file_text = '';
- $condition = ['otype' => TERM_OBJ_POST, 'oid' => $itemid, 'type' => [TERM_FILE, TERM_CATEGORY]];
- $tags = DBA::select('term', [], $condition);
- while ($tag = DBA::fetch($tags)) {
- if ($tag['type'] == TERM_CATEGORY) {
+ $tags = self::tagArrayFromItemId($item_id, [self::FILE, self::CATEGORY]);
+ foreach ($tags as $tag) {
+ if ($tag['type'] == self::CATEGORY) {
$file_text .= '<' . $tag['term'] . '>';
} else {
$file_text .= '[' . $tag['term'] . ']';
}
}
+
return $file_text;
}
- public static function insertFromTagFieldByItemId($itemid, $tags)
+ /**
+ * 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 = System::baseUrl();
$profile_data = parse_url($profile_base);
$profile_base_diaspora = $profile_data['host'] . $profile_path . '/u/';
$fields = ['guid', 'uid', 'id', 'edited', 'deleted', 'created', 'received', 'title', 'body', 'parent'];
- $message = Item::selectFirst($fields, ['id' => $itemid]);
- if (!DBA::isResult($message)) {
+ $item = Item::selectFirst($fields, ['id' => $item_id]);
+ if (!DBA::isResult($item)) {
return;
}
- $message['tag'] = $tags;
+ $item['tag'] = $tag_str;
// Clean up all tags
- self::deleteByItemId($itemid);
+ self::deleteByItemId($item_id);
- if ($message['deleted']) {
+ if ($item['deleted']) {
return;
}
- $taglist = explode(',', $message['tag']);
+ $taglist = explode(',', $item['tag']);
$tags_string = '';
foreach ($taglist as $tag) {
- if ((substr(trim($tag), 0, 1) == '#') || (substr(trim($tag), 0, 1) == '@') || (substr(trim($tag), 0, 1) == '!')) {
+ if (Strings::startsWith($tag, self::TAG_CHARACTER)) {
$tags_string .= ' ' . trim($tag);
} else {
$tags_string .= ' #' . trim($tag);
}
}
- $data = ' ' . $message['title'] . ' ' . $message['body'] . ' ' . $tags_string . ' ';
+ $data = ' ' . $item['title'] . ' ' . $item['body'] . ' ' . $tags_string . ' ';
// ignore anything in a code block
$data = preg_replace('/\[code\](.*?)\[\/code\]/sm', '', $data);
}
}
- $pattern = '/\W([\#@!])\[url\=(.*?)\](.*?)\[\/url\]/ism';
+ $pattern = '/\W([\#@!%])\[url\=(.*?)\](.*?)\[\/url\]/ism';
if (preg_match_all($pattern, $data, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
- if (($match[1] == '@') || ($match[1] == '!')) {
+ 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'];
}
}
- $tags[$match[1] . trim($match[3], ',.:;[]/\"?!')] = $match[2];
+ $tags[$match[2]] = $match[1] . trim($match[3], ',.:;[]/\"?!');
}
}
- foreach ($tags as $tag => $link) {
- if (substr(trim($tag), 0, 1) == '#') {
+ foreach ($tags as $link => $tag) {
+ if (self::isType($tag, self::HASHTAG)) {
// try to ignore #039 or #1 or anything like that
if (ctype_digit(substr(trim($tag), 1))) {
continue;
continue;
}
- $type = TERM_HASHTAG;
+ $type = self::HASHTAG;
$term = substr($tag, 1);
$link = '';
- } elseif ((substr(trim($tag), 0, 1) == '@') || (substr(trim($tag), 0, 1) == '!')) {
- $type = TERM_MENTION;
+ } elseif (self::isType($tag, self::MENTION, self::EXCLUSIVE_MENTION, self::IMPLICIT_MENTION)) {
+ if (self::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 = substr($tag, 1);
}
} else { // This shouldn't happen
- $type = TERM_HASHTAG;
+ $type = self::HASHTAG;
$term = $tag;
$link = '';
+
+ Logger::notice('Unknown term type', ['tag' => $tag]);
}
- if (DBA::exists('term', ['uid' => $message['uid'], 'otype' => TERM_OBJ_POST, 'oid' => $itemid, 'term' => $term])) {
+ if (DBA::exists('term', ['uid' => $item['uid'], 'otype' => self::OBJECT_TYPE_POST, 'oid' => $item_id, 'term' => $term, 'type' => $type])) {
continue;
}
- if ($message['uid'] == 0) {
+ if ($item['uid'] == 0) {
$global = true;
- DBA::update('term', ['global' => true], ['otype' => TERM_OBJ_POST, 'guid' => $message['guid']]);
+ DBA::update('term', ['global' => true], ['otype' => self::OBJECT_TYPE_POST, 'guid' => $item['guid']]);
} else {
- $global = DBA::exists('term', ['uid' => 0, 'otype' => TERM_OBJ_POST, 'guid' => $message['guid']]);
+ $global = DBA::exists('term', ['uid' => 0, 'otype' => self::OBJECT_TYPE_POST, 'guid' => $item['guid']]);
}
DBA::insert('term', [
- 'uid' => $message['uid'],
- 'oid' => $itemid,
- 'otype' => TERM_OBJ_POST,
+ 'uid' => $item['uid'],
+ 'oid' => $item_id,
+ 'otype' => self::OBJECT_TYPE_POST,
'type' => $type,
'term' => $term,
'url' => $link,
- 'guid' => $message['guid'],
- 'created' => $message['created'],
- 'received' => $message['received'],
+ 'guid' => $item['guid'],
+ 'created' => $item['created'],
+ 'received' => $item['received'],
'global' => $global
]);
// Search for mentions
- if (((substr($tag, 0, 1) == '@') || (substr($tag, 0, 1) == '!')) && (strpos($link, $profile_base_friendica) || strpos($link, $profile_base_diaspora))) {
- $users = q("SELECT `uid` FROM `contact` WHERE self AND (`url` = '%s' OR `nurl` = '%s')", $link, $link);
+ if (self::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'] == $message['uid']) {
- /// @todo This function is called frim Item::update - so we mustn't call that function here
- DBA::update('item', ['mention' => true], ['id' => $itemid]);
- DBA::update('thread', ['mention' => true], ['iid' => $message['parent']]);
+ 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']]);
}
}
}
}
/**
- * @param integer $itemid item id
+ * 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.
+ *
+ * @param integer $item_id item id
* @param $files
* @return void
* @throws \Exception
*/
- public static function insertFromFileFieldByItemId($itemid, $files)
+ public static function insertFromFileFieldByItemId($item_id, $files)
{
- $message = Item::selectFirst(['uid', 'deleted'], ['id' => $itemid]);
+ $message = Item::selectFirst(['uid', 'deleted'], ['id' => $item_id]);
if (!DBA::isResult($message)) {
return;
}
// Clean up all tags
- DBA::delete('term', ['otype' => TERM_OBJ_POST, 'oid' => $itemid, 'type' => [TERM_FILE, TERM_CATEGORY]]);
+ DBA::delete('term', ['otype' => self::OBJECT_TYPE_POST, 'oid' => $item_id, 'type' => [self::FILE, self::CATEGORY]]);
if ($message["deleted"]) {
return;
foreach ($files[1] as $file) {
DBA::insert('term', [
'uid' => $message["uid"],
- 'oid' => $itemid,
- 'otype' => TERM_OBJ_POST,
- 'type' => TERM_FILE,
+ 'oid' => $item_id,
+ 'otype' => self::OBJECT_TYPE_POST,
+ 'type' => self::FILE,
'term' => $file
]);
}
foreach ($files[1] as $file) {
DBA::insert('term', [
'uid' => $message["uid"],
- 'oid' => $itemid,
- 'otype' => TERM_OBJ_POST,
- 'type' => TERM_CATEGORY,
+ 'oid' => $item_id,
+ 'otype' => self::OBJECT_TYPE_POST,
+ 'type' => self::CATEGORY,
'term' => $file
]);
}
'tags' => [],
'hashtags' => [],
'mentions' => [],
+ 'implicit_mentions' => [],
];
$searchpath = System::baseUrl() . "/search?tag=";
$taglist = DBA::select(
'term',
['type', 'term', 'url'],
- ["`otype` = ? AND `oid` = ? AND `type` IN (?, ?)", TERM_OBJ_POST, $item['id'], TERM_HASHTAG, TERM_MENTION],
+ ['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'];
- $author = ['uid' => 0, 'id' => $item['author-id'],
- 'network' => $item['author-network'], 'url' => $item['author-link']];
-
- $prefix = '';
- if ($tag['type'] == TERM_HASHTAG) {
- $tag['url'] = Contact::magicLinkByContact($author, $tag['url']);
- if ($orig_tag != $tag['url']) {
- $item['body'] = str_replace($orig_tag, $tag['url'], $item['body']);
- }
+ $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'][] = '#<a href="' . $tag['url'] . '" target="_blank">' . $tag['term'] . '</a>';
- $prefix = '#';
- } elseif ($tag['type'] == TERM_MENTION) {
- $tag['url'] = Contact::magicLink($tag['url']);
- $return['mentions'][] = '@<a href="' . $tag['url'] . '" target="_blank">' . $tag['term'] . '</a>';
- $prefix = '@';
+ $return['hashtags'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank">' . $tag['term'] . '</a>';
+ $return['tags'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank">' . $tag['term'] . '</a>';
+ break;
+ case self::MENTION:
+ $tag['url'] = Contact::magicLink($tag['url']);
+ $return['mentions'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank">' . $tag['term'] . '</a>';
+ $return['tags'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank">' . $tag['term'] . '</a>';
+ break;
+ case self::IMPLICIT_MENTION:
+ $return['implicit_mentions'][] = $prefix . $tag['term'];
+ break;
}
-
- $return['tags'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank">' . $tag['term'] . '</a>';
}
DBA::close($taglist);
}
/**
- * Delete all tags from an item
+ * Delete tags of the specific type(s) from an item
*
- * @param int itemid - choose from which item the tags will be removed
- * @param array $type
+ * @param int $item_id
+ * @param int|array $type
* @throws \Exception
*/
- public static function deleteByItemId($itemid, $type = [TERM_HASHTAG, TERM_MENTION])
+ public static function deleteByItemId($item_id, $type = [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION])
{
- if (empty($itemid)) {
+ if (empty($item_id)) {
return;
}
// Clean up all tags
- DBA::delete('term', ['otype' => TERM_OBJ_POST, 'oid' => $itemid, 'type' => $type]);
+ 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 (isset(self::TAG_CHARACTER[$type])) {
+ $tag_chars[] = self::TAG_CHARACTER[$type];
+ }
+ }
+ return Strings::startsWith($tag, $tag_chars);
}
}
namespace Friendica\Module;
+use Friendica\Content\Text\HTML;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
use Friendica\Model;
+use Friendica\Protocol\ActivityPub\Processor;
+use Friendica\Protocol\Diaspora;
/**
* @author Hypolite Petovan <mrpetovan@gmail.com>
$source = '';
$item_uri = '';
+ $item_id = '';
+ $terms = [];
if (!empty($guid)) {
- $item = Model\Item::selectFirst([], ['guid' => $guid]);
+ $item = Model\Item::selectFirst(['id', 'guid', 'uri'], ['guid' => $guid]);
$conversation = Model\Conversation::getByItemUri($item['uri']);
+ $guid = $item['guid'];
+ $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]);
}
$tpl = Renderer::getMarkupTemplate('debug/itemsource.tpl');
$o = Renderer::replaceMacros($tpl, [
- '$guid' => ['guid', L10n::t('Item Guid'), defaults($_REQUEST, 'guid', ''), ''],
+ '$guid' => ['guid', L10n::t('Item Guid'), $guid, ''],
'$source' => $source,
- '$item_uri' => $item_uri
+ '$item_uri' => $item_uri,
+ '$item_id' => $item_id,
+ '$terms' => $terms,
]);
return $o;
$author = ['uid' => 0, 'id' => $this->getDataValue('author-id'),
'network' => $this->getDataValue('author-network'),
'url' => $this->getDataValue('author-link')];
- $this->redirect_url = Contact::magicLinkbyContact($author);
+ $this->redirect_url = Contact::magicLinkByContact($author);
if (!$this->isToplevel()) {
$this->threaded = true;
}
'network' => $item['author-network'], 'url' => $item['author-link']];
if (local_user() || remote_user()) {
- $profile_link = Contact::magicLinkbyContact($author);
+ $profile_link = Contact::magicLinkByContact($author);
} else {
$profile_link = $item['author-link'];
}
'tags' => $tags['tags'],
'hashtags' => $tags['hashtags'],
'mentions' => $tags['mentions'],
+ 'implicit_mentions' => $tags['implicit_mentions'],
'txt_cats' => L10n::t('Categories:'),
'txt_folders' => L10n::t('Filed under:'),
'has_cats' => ((count($categories)) ? 'true' : ''),
$text = '';
}
- $terms = Term::tagArrayFromItemId($this->getId(), TERM_MENTION);
+ $terms = Term::tagArrayFromItemId($this->getId(), [Term::MENTION, Term::IMPLICIT_MENTION]);
foreach ($terms as $term) {
$profile = Contact::getDetailsByURL($term['url']);
$owner = ['uid' => 0, 'id' => $this->getDataValue('owner-id'),
'network' => $this->getDataValue('owner-network'),
'url' => $this->getDataValue('owner-link')];
- $this->owner_url = Contact::magicLinkbyContact($owner);
+ $this->owner_url = Contact::magicLinkByContact($owner);
}
}
}
* @param array $implicit_mentions List of profile URLs to skip
* @return string with tags
*/
- private static function constructTagString($tags, $sensitive, array $implicit_mentions)
+ private static function constructTagString(array $tags, $sensitive)
{
if (empty($tags)) {
return '';
$tag_text = '';
foreach ($tags as $tag) {
- if (in_array(defaults($tag, 'type', ''), ['Mention', 'Hashtag']) && !in_array($tag['href'], $implicit_mentions)) {
+ if (in_array(defaults($tag, 'type', ''), ['Mention', 'Hashtag'])) {
if (!empty($tag_text)) {
$tag_text .= ',';
}
*/
public static function updateItem($activity)
{
- $item = Item::selectFirst(['uri', 'parent-uri', 'gravity'], ['uri' => $activity['id']]);
+ $item = Item::selectFirst(['uri', 'thr-parent', 'gravity'], ['uri' => $activity['id']]);
if (!DBA::isResult($item)) {
Logger::warning('Unknown item', ['uri' => $activity['id']]);
return;
$content = self::replaceEmojis($content, $activity['emojis']);
$content = self::convertMentions($content);
- $implicit_mentions = [];
- if (($item['parent-uri'] != $item['uri']) && ($item['gravity'] == GRAVITY_COMMENT)) {
- $parent = Item::selectFirst(['id', 'author-link', 'alias'], ['uri' => $item['parent-uri']]);
+ if (($item['thr-parent'] != $item['uri']) && ($item['gravity'] == GRAVITY_COMMENT)) {
+ $parent = Item::selectFirst(['id', 'author-link', 'alias'], ['uri' => $item['thr-parent']]);
if (!DBA::isResult($parent)) {
- Logger::warning('Unknown parent item.', ['uri' => $item['parent-uri']]);
+ Logger::warning('Unknown parent item.', ['uri' => $item['thr-parent']]);
return;
}
- $implicit_mentions = self::getImplicitMentionList($parent);
- $content = self::removeImplicitMentionsFromBody($content, $implicit_mentions);
+ $potential_implicit_mentions = self::getImplicitMentionList($parent);
+ $content = self::removeImplicitMentionsFromBody($content, $potential_implicit_mentions);
+ $activity['tags'] = self::convertImplicitMentionsInTags($activity['tags'], $potential_implicit_mentions);
}
$item['body'] = $content;
- $item['tag'] = self::constructTagString($activity['tags'], $activity['sensitive'], $implicit_mentions);
+ $item['tag'] = self::constructTagString($activity['tags'], $activity['sensitive']);
Item::update($item, ['uri' => $activity['id']]);
}
{
$item = [];
$item['verb'] = ACTIVITY_POST;
- $item['parent-uri'] = $activity['reply-to-id'];
+ $item['thr-parent'] = $activity['reply-to-id'];
if ($activity['reply-to-id'] == $activity['id']) {
$item['gravity'] = GRAVITY_PARENT;
{
$item = [];
$item['verb'] = $verb;
- $item['parent-uri'] = $activity['object_id'];
+ $item['thr-parent'] = $activity['object_id'];
$item['gravity'] = GRAVITY_ACTIVITY;
$item['object-type'] = ACTIVITY_OBJ_NOTE;
{
/// @todo What to do with $activity['context']?
- if (($item['gravity'] != GRAVITY_PARENT) && !Item::exists(['uri' => $item['parent-uri']])) {
- Logger::log('Parent ' . $item['parent-uri'] . ' not found, message will be discarded.', Logger::DEBUG);
+ if (($item['gravity'] != GRAVITY_PARENT) && !Item::exists(['uri' => $item['thr-parent']])) {
+ Logger::info('Parent not found, message will be discarded.', ['thr-parent' => $item['thr-parent']]);
return;
}
$item['owner-link'] = $activity['actor'];
$item['owner-id'] = Contact::getIdForURL($activity['actor'], 0, true);
} else {
- Logger::log('Ignoring actor because of thread completion.', Logger::DEBUG);
+ Logger::info('Ignoring actor because of thread completion.');
$item['owner-link'] = $item['author-link'];
$item['owner-id'] = $item['author-id'];
}
$content = self::replaceEmojis($content, $activity['emojis']);
$content = self::convertMentions($content);
- $implicit_mentions = [];
-
- if (($item['parent-uri'] != $item['uri']) && ($item['gravity'] == GRAVITY_COMMENT)) {
+ if (($item['thr-parent'] != $item['uri']) && ($item['gravity'] == GRAVITY_COMMENT)) {
$item_private = !in_array(0, $activity['item_receiver']);
- $parent = Item::selectFirst(['id', 'private', 'author-link', 'alias'], ['uri' => $item['parent-uri']]);
+ $parent = Item::selectFirst(['id', 'private', 'author-link', 'alias'], ['uri' => $item['thr-parent']]);
if (!DBA::isResult($parent)) {
return;
}
if ($item_private && !$parent['private']) {
- Logger::log('Item ' . $item['uri'] . ' is private but the parent ' . $item['parent-uri'] . ' is not. So we drop it.');
+ Logger::warning('Item is private but the parent is not. Dropping.', ['item-uri' => $item['uri'], 'thr-parent' => $item['thr-parent']]);
return;
}
- $implicit_mentions = self::getImplicitMentionList($parent);
- $content = self::removeImplicitMentionsFromBody($content, $implicit_mentions);
+ $potential_implicit_mentions = self::getImplicitMentionList($parent);
+ $content = self::removeImplicitMentionsFromBody($content, $potential_implicit_mentions);
+ $activity['tags'] = self::convertImplicitMentionsInTags($activity['tags'], $potential_implicit_mentions);
}
$item['created'] = $activity['published'];
$item['coord'] = $item['latitude'] . ' ' . $item['longitude'];
}
- $item['tag'] = self::constructTagString($activity['tags'], $activity['sensitive'], $implicit_mentions);
+ $item['tag'] = self::constructTagString($activity['tags'], $activity['sensitive']);
$item['app'] = $activity['generator'];
$item['plink'] = defaults($activity, 'alternate-url', $item['uri']);
}
$item_id = Item::insert($item);
- Logger::log('Storing for user ' . $item['uid'] . ': ' . $item_id);
+ if ($item_id) {
+ Logger::info('Item insertion successful', ['user' => $item['uid'], 'item_id' => $item_id]);
+ } else {
+ Logger::notice('Item insertion aborted', ['user' => $item['uid']]);
+ }
if ($item['uid'] == 0) {
$stored = $item_id;
*/
private static function getImplicitMentionList(array $parent)
{
- $parent_terms = Term::tagArrayFromItemId($parent['id'], [TERM_MENTION]);
+ if (Config::get('system', 'disable_implicit_mentions')) {
+ return [];
+ }
+
+ $parent_terms = Term::tagArrayFromItemId($parent['id'], [Term::MENTION, Term::IMPLICIT_MENTION]);
+
+ $parent_author = Contact::getDetailsByURL($parent['author-link'], 0);
$implicit_mentions = [
- $parent['author-link']
+ $parent_author['url'],
+ $parent_author['nurl'],
+ $parent_author['alias'],
];
if ($parent['alias']) {
* Strips from the body prepended implicit mentions
*
* @param string $body
- * @param array $implicit_mentions List of profile URLs
+ * @param array $potential_mentions
* @return string
*/
- private static function removeImplicitMentionsFromBody($body, array $implicit_mentions)
+ private static function removeImplicitMentionsFromBody($body, array $potential_mentions)
{
- if (Config::get('system', 'disable_mentions_removal')) {
+ if (Config::get('system', 'disable_implicit_mentions')) {
return $body;
}
// Extract one prepended mention at a time from the body
while(preg_match('#^(@\[url=([^\]]+)].*?\[\/url]\s)(.*)#mis', $body, $matches)) {
- if (!in_array($matches[2], $implicit_mentions) ) {
+ if (!in_array($matches[2], $potential_mentions) ) {
$kept_mentions[] = $matches[1];
}
return implode('', $kept_mentions);
}
+
+ private static function convertImplicitMentionsInTags($activity_tags, array $potential_mentions)
+ {
+ if (Config::get('system', 'disable_implicit_mentions')) {
+ return $activity_tags;
+ }
+
+ 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],
+ $activity_tags[$index]['name'],
+ 1
+ );
+ }
+ }
+
+ return $activity_tags;
+ }
+
+ public static function testImplicitMentions($item, $source)
+ {
+ $parent = Item::selectFirst(['id', 'guid', 'author-link', 'alias'], ['uri' => $item['thr-parent']]);
+
+ $implicit_mentions = self::getImplicitMentionList($parent);
+ var_dump($implicit_mentions);
+
+ $object = json_decode($source, true)['object'];
+ var_dump($object);
+
+ $content = HTML::toBBCode($object['content']);
+ $content = self::convertMentions($content);
+
+ $activity = [
+ 'tags' => $object['tag'],
+ 'content' => $content
+ ];
+
+ var_dump($activity);
+
+ $activity['content'] = Processor::removeImplicitMentionsFromBody($activity['content'], $implicit_mentions);
+ $activity['tags'] = Processor::convertImplicitMentionsInTags($activity['tags'], $implicit_mentions);
+
+ return $activity;
+ }
}
{
$http_signer = HTTPSignature::getSigner($body, $header);
if (empty($http_signer)) {
- Logger::log('Invalid HTTP signature, message will be discarded.', Logger::DEBUG);
+ Logger::warning('Invalid HTTP signature, message will be discarded.');
return;
} else {
- Logger::log('HTTP signature is signed by ' . $http_signer, Logger::DEBUG);
+ Logger::info('Valid HTTP signature', ['signer' => $http_signer]);
}
$activity = json_decode($body, true);
if (empty($activity)) {
- Logger::log('Invalid body.', Logger::DEBUG);
+ Logger::warning('Invalid body.');
return;
}
$actor = JsonLD::fetchElement($ldactivity, 'as:actor');
- Logger::log('Message for user ' . $uid . ' is from actor ' . $actor, Logger::DEBUG);
+ Logger::info('Message for user ' . $uid . ' is from actor ' . $actor);
if (LDSignature::isSigned($activity)) {
$ld_signer = LDSignature::getSigner($activity);
$actor_profile = APContact::getByURL($item['author-link']);
}
- $terms = Term::tagArrayFromItemId($item['id'], TERM_MENTION);
+ $terms = Term::tagArrayFromItemId($item['id'], [Term::MENTION, Term::IMPLICIT_MENTION]);
if (!$item['private']) {
$data = array_merge($data, self::fetchPermissionBlockFromConversation($item));
{
$tags = [];
- $terms = Term::tagArrayFromItemId($item['id']);
+ $terms = Term::tagArrayFromItemId($item['id'], [Term::HASHTAG, Term::MENTION, Term::IMPLICIT_MENTION]);
foreach ($terms as $term) {
- if ($term['type'] == TERM_HASHTAG) {
+ if ($term['type'] == Term::HASHTAG) {
$url = System::baseUrl() . '/search?tag=' . urlencode($term['term']);
$tags[] = ['type' => 'Hashtag', 'href' => $url, 'name' => '#' . $term['term']];
- } elseif ($term['type'] == TERM_MENTION) {
+ } elseif ($term['type'] == Term::MENTION || $term['type'] == Term::IMPLICIT_MENTION) {
$contact = Contact::getDetailsByURL($term['url']);
if (!empty($contact['addr'])) {
$mention = '@' . $contact['addr'];
private static function prependMentions($body, array $permission_block)
{
+ if (Config::get('system', 'disable_implicit_mentions')) {
+ return $body;
+ }
+
$mentions = [];
foreach ($permission_block['to'] as $profile_url) {
$author["network"] = $contact_old["network"];
} else {
if (!$onlyfetch) {
- Logger::log("Contact ".$author["link"]." wasn't found for user ".$importer["importer_uid"]." XML: ".$xml, Logger::DEBUG);
+ Logger::debug("Contact ".$author["link"]." wasn't found for user ".$importer["importer_uid"]." XML: ".$xml);
}
$author["contact-unknown"] = true;
$person = DBA::selectFirst('fcontact', [], ['network' => Protocol::DIASPORA, 'addr' => $handle]);
if (DBA::isResult($person)) {
- Logger::log("In cache " . print_r($person, true), Logger::DEBUG);
+ Logger::debug("In cache " . print_r($person, true));
// update record occasionally so it doesn't get stale
$d = strtotime($person["updated"]." +00:00");
&& !strstr($body, $profile['addr'])
&& !strstr($body, $profile_url)
) {
- $body = '@[url=' . $profile_url . ']' . $profile['nick'] . '[/url] ' . $body;
+ $body = '@[url=' . $profile_url . ']' . $profile['name'] . '[/url] ' . $body;
}
return $body;
* @param array $item The item that will be exported
* @param array $owner the array of the item owner
*
- * @return array The data for a comment
+ * @return array|false The data for a comment
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private static function constructComment(array $item, array $owner)
return $result;
}
- $parent = Item::selectFirst(['guid', 'author-link'], ['id' => $item["parent"], 'parent' => $item["parent"]]);
- if (!DBA::isResult($parent)) {
+ $toplevel_item = Item::selectFirst(['guid', 'author-link'], ['id' => $item["parent"], 'parent' => $item["parent"]]);
+ if (!DBA::isResult($toplevel_item)) {
+ Logger::error('Missing parent conversation item', ['parent' => $item["parent"]]);
return false;
}
+ $thread_parent_item = $toplevel_item;
+ if ($item['thr-parent'] != $item['parent-uri']) {
+ $thread_parent_item = Item::selectFirst(['guid', 'author-link'], ['uri' => $item['thr-parent'], 'uid' => $item['uid']]);
+ }
+
$body = $item["body"];
- if (empty($item['uid']) || !Feature::isEnabled($item['uid'], 'explicit_mentions')) {
- $body = self::prependParentAuthorMention($body, $parent['author-link']);
+ if ((empty($item['uid']) || !Feature::isEnabled($item['uid'], 'explicit_mentions'))
+ && !Config::get('system', 'disable_implicit_mentions')
+ ) {
+ $body = self::prependParentAuthorMention($body, $thread_parent_item['author-link']);
}
$text = html_entity_decode(BBCode::toMarkdown($body));
$created = DateTimeFormat::utc($item["created"], DateTimeFormat::ATOM);
- $comment = ["author" => self::myHandle($owner),
- "guid" => $item["guid"],
- "created_at" => $created,
- "parent_guid" => $parent["guid"],
- "text" => $text,
- "author_signature" => ""];
+ $comment = [
+ "author" => self::myHandle($owner),
+ "guid" => $item["guid"],
+ "created_at" => $created,
+ "parent_guid" => $toplevel_item["guid"],
+ "text" => $text,
+ "author_signature" => ""
+ ];
// Send the thread parent guid only if it is a threaded comment
if ($item['thr-parent'] != $item['parent-uri']) {
- $comment['thread_parent_guid'] = self::getGuidFromUri($item['thr-parent'], $item['uid']);
+ $comment['thread_parent_guid'] = $thread_parent_item['guid'];
}
Cache::set($cachekey, $comment, Cache::QUARTER_HOUR);
return $uri;
}
+
+
+ /**
+ * Check if the trimmed provided string is starting with one of the provided characters
+ *
+ * @param string $string
+ * @param array $chars
+ * @return bool
+ */
+ public static function startsWith($string, array $chars)
+ {
+ $return = in_array(substr(trim($string), 0, 1), $chars);
+
+ return $return;
+ }
}
use Friendica\Core\Logger;
use DOMXPath;
+use Friendica\Core\System;
use SimpleXMLElement;
/**
$x = @simplexml_load_string($s);
if (!$x) {
- Logger::log('libxml: parse: error: ' . $s, Logger::DATA);
+ Logger::error('Error(s) while parsing XML string.', ['callstack' => System::callstack()]);
foreach (libxml_get_errors() as $err) {
- Logger::log('libxml: parse: ' . $err->code." at ".$err->line.":".$err->column." : ".$err->message, Logger::DATA);
+ Logger::info('libxml error', ['code' => $err->code, 'position' => $err->line . ":" . $err->column, 'message' => $err->message]);
}
+ Logger::debug('Erroring XML string', ['xml' => $s]);
libxml_clear_errors();
}
return $x;
{{if $source}}
<div class="itemsource-results">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title">Item Id</h3>
+ </div>
+ <div class="panel-body">
+ {{$item_id}}
+ </div>
+ </div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Item URI</h3>
{{$item_uri}}
</div>
</div>
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title">Terms</h3>
+ </div>
+ <div class="panel-body">
+ <table class="table table-condensed table-striped">
+ <tr>
+ <th>Type</th>
+ <th>Term</th>
+ <th>URL</th>
+ </tr>
+ {{foreach $terms as $term}}
+ <tr>
+ <td>
+ {{if $term.type == 1}}Tag{{/if}}
+ {{if $term.type == 2}}Mention{{/if}}
+ {{if $term.type == 8}}Implicit Mention{{/if}}
+ </td>
+ <td>
+ {{$term.term}}
+ </td>
+ <td>
+ {{$term.url}}
+ </td>
+ </tr>
+ {{/foreach}}
+ </table>
+ </div>
+ </div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Source</h3>
{{foreach $item.mentions as $tag}}
<span class="mention label btn-warning sm">{{$tag nofilter}} <i class="fa fa-user" aria-hidden="true"></i></span>
{{/foreach}}
- {{/if}}
+ {{*foreach $item.implicit_mentions as $tag}}
+ <span class="mention label label-default sm">{{$tag nofilter}} <i class="fa fa-eye-slash" aria-hidden="true"></i></span>
+ {{/foreach*}}
+ {{/if}}
{{foreach $item.folders as $cat}}
<span class="folder label btn-danger sm p-category">{{$cat.name}}{{if $cat.removeurl}} (<a href="{{$cat.removeurl}}" title="{{$remove}}">x</a>) {{/if}} </span>
{{/foreach}}