namespace Friendica\Protocol;
use Friendica\Content\Feature;
+use Friendica\Content\PageInfo;
use Friendica\Content\Text\BBCode;
use Friendica\Content\Text\Markdown;
use Friendica\Core\Cache\Duration;
use Friendica\Model\GContact;
use Friendica\Model\Item;
use Friendica\Model\ItemURI;
-use Friendica\Model\ItemDeliveryData;
use Friendica\Model\Mail;
-use Friendica\Model\Profile;
+use Friendica\Model\Post;
use Friendica\Model\Tag;
-use Friendica\Model\Term;
use Friendica\Model\User;
use Friendica\Network\Probe;
use Friendica\Util\Crypto;
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;
}
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);
* One of the parameters is a contact array.
* This is done to avoid duplicates.
*
- * @param integer $thread The id of the thread
- * @param array $contacts The previously fetched contacts
+ * @param array $item Item that is about to be delivered
+ * @param array $contacts The previously fetched contacts
*
* @return array of relay servers
* @throws \Exception
*/
- public static function participantsForThread($thread, array $contacts)
+ public static function participantsForThread(array $item, array $contacts)
{
- $participation = DBA::select('participation-view', [], ['iid' => $thread]);
-
- while ($contact = DBA::fetch($participation)) {
- if (empty($contact['protocol'])) {
- $contact['protocol'] = $contact['network'];
+ if (!in_array($item['private'], [Item::PUBLIC, Item::UNLISTED]) || in_array($item["verb"], [Activity::FOLLOW, Activity::TAG])) {
+ Logger::info('Item is private or a participation request. It will not be relayed', ['guid' => $item['guid'], 'private' => $item['private'], 'verb' => $item['verb']]);
+ return $contacts;
+ }
+
+ $items = Item::select(['author-id', 'author-link', 'parent-author-link', 'parent-guid', 'guid'],
+ ['parent' => $item['parent'], 'gravity' => [GRAVITY_COMMENT, GRAVITY_ACTIVITY]]);
+ while ($item = DBA::fetch($items)) {
+ $contact = DBA::selectFirst('contact', ['id', 'url', 'name', 'protocol', 'batch', 'network'],
+ ['id' => $item['author-id']]);
+ if (!DBA::isResult($contact) || empty($contact['batch']) ||
+ ($contact['network'] != Protocol::DIASPORA) ||
+ Strings::compareLink($item['parent-author-link'], $item['author-link'])) {
+ continue;
}
$exists = false;
}
if (!$exists) {
+ Logger::info('Add participant to receiver list', ['parent' => $item['parent-guid'], 'item' => $item['guid'], 'participant' => $contact['url']]);
$contacts[] = $contact;
}
}
-
- DBA::close($participation);
+ DBA::close($items);
return $contacts;
}
- /**
- * repairs a signature that was double encoded
- *
- * The function is unused at the moment. It was copied from the old implementation.
- *
- * @param string $signature The signature
- * @param string $handle The handle of the signature owner
- * @param integer $level This value is only set inside this function to avoid endless loops
- *
- * @return string the repaired signature
- * @throws \Exception
- */
- private static function repairSignature($signature, $handle = "", $level = 1)
- {
- if ($signature == "") {
- return ($signature);
- }
-
- if (base64_encode(base64_decode(base64_decode($signature))) == base64_decode($signature)) {
- $signature = base64_decode($signature);
- Logger::log("Repaired double encoded signature from Diaspora/Hubzilla handle ".$handle." - level ".$level, Logger::DEBUG);
-
- // Do a recursive call to be able to fix even multiple levels
- if ($level < 10) {
- $signature = self::repairSignature($signature, $handle, ++$level);
- }
- }
-
- return($signature);
- }
-
/**
* verify the envelope and return the verified data
*
$basedom = XML::parseString($xml);
if (!is_object($basedom)) {
- Logger::log("XML is not parseable.");
+ Logger::notice('XML is not parseable.');
return false;
}
$children = $basedom->children('https://joindiaspora.com/protocol');
} else {
// This happens with posts from a relais
if (empty($privKey)) {
- Logger::log("This is no private post in the old format", Logger::DEBUG);
+ Logger::info('This is no private post in the old format');
return false;
}
$decrypted = self::aesDecrypt($outer_key, $outer_iv, $ciphertext);
- Logger::log('decrypted: '.$decrypted, Logger::DEBUG);
+ Logger::info('decrypted', ['data' => $decrypted]);
$idom = XML::parseString($decrypted);
$inner_iv = base64_decode($idom->iv);
$type = $fields->getName();
- Logger::log("Received message type ".$type." from ".$sender." for user ".$importer["uid"], Logger::DEBUG);
+ Logger::info('Received message', ['type' => $type, 'sender' => $sender, 'user' => $importer["uid"]]);
switch ($type) {
case "account_migration":
$data = XML::parseString($msg["message"]);
if (!is_object($data)) {
- Logger::log("No valid XML ".$msg["message"], Logger::DEBUG);
+ Logger::info('No valid XML', ['message' => $msg['message']]);
return false;
}
if (isset($parent_author_signature)) {
$key = self::key($msg["author"]);
if (empty($key)) {
- Logger::log("No key found for parent author ".$msg["author"], Logger::DEBUG);
+ Logger::info('No key found for parent', ['author' => $msg["author"]]);
return false;
}
$key = self::key($fields->author);
if (empty($key)) {
- Logger::log("No key found for author ".$fields->author, Logger::DEBUG);
+ Logger::info('No key found', ['author' => $fields->author]);
return false;
}
}
if (DBA::isResult($person)) {
- Logger::debug("In cache " . print_r($person, true));
+ Logger::debug('In cache', ['person' => $person]);
if (is_null($update)) {
// update record occasionally so it doesn't get stale
*/
public static function urlFromContactGuid($fcontact_guid)
{
- Logger::log("fcontact guid is ".$fcontact_guid, Logger::DEBUG);
+ Logger::info('fcontact', ['guid' => $fcontact_guid]);
$r = q(
"SELECT `url` FROM `fcontact` WHERE `url` != '' AND `network` = '%s' AND `guid` = '%s'",
Logger::log("Fetch post from ".$source_url, Logger::DEBUG);
- $envelope = Network::fetchUrl($source_url);
+ $envelope = DI::httpRequest()->fetch($source_url);
if ($envelope) {
Logger::log("Envelope was fetched.", Logger::DEBUG);
$x = self::verifyMagicEnvelope($envelope);
private static function parentItem($uid, $guid, $author, array $contact)
{
$fields = ['id', 'parent', 'body', 'wall', 'uri', 'guid', 'private', 'origin',
- 'author-name', 'author-link', 'author-avatar',
+ 'author-name', 'author-link', 'author-avatar', 'gravity',
'owner-name', 'owner-link', 'owner-avatar'];
$condition = ['uid' => $uid, 'guid' => $guid];
$item = Item::selectFirst($fields, $condition);
*/
private static function plink($addr, $guid, $parent_guid = '')
{
- $contact = Contact::getDetailsByAddr($addr);
+ $contact = Contact::getByURL($addr);
if (empty($contact)) {
Logger::info('No contact data for address', ['addr' => $addr]);
return '';
$datarray["changed"] = $datarray["created"] = $datarray["edited"] = DateTimeFormat::utcNow();
// like on comments have the comment as parent. So we need to fetch the toplevel parent
- if ($parent_item["id"] != $parent_item["parent"]) {
- $toplevel = Item::selectFirst(['origin'], ['id' => $parent_item["parent"]]);
+ if ($parent_item['gravity'] != GRAVITY_PARENT) {
+ $toplevel = Item::selectFirst(['origin'], ['id' => $parent_item['parent']]);
$origin = $toplevel["origin"];
} else {
$origin = $parent_item["origin"];
* @param array $importer Array of the importer user
* @param object $data The message object
*
- * @return bool always true
+ * @return bool success
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
private static function receiveParticipation(array $importer, $data)
{
$author = strtolower(Strings::escapeTags(XML::unescape($data->author)));
+ $guid = Strings::escapeTags(XML::unescape($data->guid));
$parent_guid = Strings::escapeTags(XML::unescape($data->parent_guid));
- $contact_id = Contact::getIdForURL($author);
- if (!$contact_id) {
- Logger::log('Contact not found: '.$author);
+ $contact = self::allowedContactByHandle($importer, $author, true);
+ if (!$contact) {
return false;
}
- $person = self::personByHandle($author);
- if (!is_array($person)) {
- Logger::log("Person not found: ".$author);
- return false;
+ if (self::messageExists($importer["uid"], $guid)) {
+ return true;
}
- $item = Item::selectFirst(['id'], ['guid' => $parent_guid, 'origin' => true, 'private' => [Item::PUBLIC, Item::UNLISTED]]);
- if (!DBA::isResult($item)) {
- Logger::log('Item not found, no origin or private: '.$parent_guid);
+ $parent_item = self::parentItem($importer["uid"], $parent_guid, $author, $contact);
+ if (!$parent_item) {
return false;
}
- $author_parts = explode('@', $author);
- if (isset($author_parts[1])) {
- $server = $author_parts[1];
- } else {
- // Should never happen
- $server = $author;
+ if (!$parent_item['origin']) {
+ Logger::info('Not our origin. Participation is ignored', ['parent_guid' => $parent_guid, 'guid' => $guid, 'author' => $author]);
}
- Logger::log('Received participation for ID: '.$item['id'].' - Contact: '.$contact_id.' - Server: '.$server, Logger::DEBUG);
+ if (!in_array($parent_item['private'], [Item::PUBLIC, Item::UNLISTED])) {
+ Logger::info('Item is not public, participation is ignored', ['parent_guid' => $parent_guid, 'guid' => $guid, 'author' => $author]);
+ return false;
+ }
- if (!DBA::exists('participation', ['iid' => $item['id'], 'server' => $server])) {
- DBA::insert('participation', ['iid' => $item['id'], 'cid' => $contact_id, 'fid' => $person['id'], 'server' => $server]);
+ $person = self::personByHandle($author);
+ if (!is_array($person)) {
+ Logger::log("Person not found: ".$author);
+ return false;
}
+ $author_contact = self::authorContactByUrl($contact, $person, $importer["uid"]);
+
+ // Store participation
+ $datarray = [];
+
+ $datarray["protocol"] = Conversation::PARCEL_DIASPORA;
+
+ $datarray["uid"] = $importer["uid"];
+ $datarray["contact-id"] = $author_contact["cid"];
+ $datarray["network"] = $author_contact["network"];
+
+ $datarray["owner-link"] = $datarray["author-link"] = $person["url"];
+ $datarray["owner-id"] = $datarray["author-id"] = Contact::getIdForURL($person["url"], 0);
+
+ $datarray["guid"] = $guid;
+ $datarray["uri"] = self::getUriFromGuid($author, $guid);
+
+ $datarray["verb"] = Activity::FOLLOW;
+ $datarray["gravity"] = GRAVITY_ACTIVITY;
+ $datarray["parent-uri"] = $parent_item["uri"];
+
+ $datarray["object-type"] = Activity\ObjectType::NOTE;
+
+ $datarray["body"] = Activity::FOLLOW;
+
+ // Diaspora doesn't provide a date for a participation
+ $datarray["changed"] = $datarray["created"] = $datarray["edited"] = DateTimeFormat::utcNow();
+
+ $message_id = Item::insert($datarray);
+
+ Logger::info('Participation stored', ['id' => $message_id, 'guid' => $guid, 'parent_guid' => $parent_guid, 'author' => $author]);
+
// Send all existing comments and likes to the requesting server
- $comments = Item::select(['id', 'parent', 'verb', 'self'], ['parent' => $item['id']]);
+ $comments = Item::select(['id', 'uri-id', 'parent-author-network', 'author-network', 'verb'],
+ ['parent' => $parent_item['id'], 'gravity' => [GRAVITY_COMMENT, GRAVITY_ACTIVITY]]);
while ($comment = Item::fetch($comments)) {
- if ($comment['id'] == $comment['parent']) {
+ if (in_array($comment['verb'], [Activity::FOLLOW, Activity::TAG])) {
+ Logger::info('participation messages are not relayed', ['item' => $comment['id']]);
continue;
}
- Logger::info('Deliver participation', ['item' => $comment['id'], 'contact' => $contact_id]);
- if (Worker::add(PRIORITY_HIGH, 'Delivery', Delivery::POST, $comment['id'], $contact_id)) {
- ItemDeliveryData::incrementQueueCount($comment['id'], 1);
+ if ($comment['author-network'] == Protocol::ACTIVITYPUB) {
+ Logger::info('Comments from ActivityPub authors are not relayed', ['item' => $comment['id']]);
+ continue;
+ }
+
+ if ($comment['parent-author-network'] == Protocol::ACTIVITYPUB) {
+ Logger::info('Comments to comments from ActivityPub authors are not relayed', ['item' => $comment['id']]);
+ continue;
+ }
+
+ Logger::info('Deliver participation', ['item' => $comment['id'], 'contact' => $author_contact["cid"]]);
+ if (Worker::add(PRIORITY_HIGH, 'Delivery', Delivery::POST, $comment['id'], $author_contact["cid"])) {
+ Post\DeliveryData::incrementQueueCount($comment['uri-id'], 1);
}
}
DBA::close($comments);
$image_url = "http://".$handle_parts[1].$image_url;
}
- Contact::updateAvatar($image_url, $importer["uid"], $contact["id"]);
+ Contact::updateAvatar($contact["id"], $image_url);
// Generic birthday. We don't know the timezone. The year is irrelevant.
}
// Do we already have this item?
- $fields = ['body', 'title', 'attach', 'tag', 'app', 'created', 'object-type', 'uri', 'guid',
- 'author-name', 'author-link', 'author-avatar'];
+ $fields = ['body', 'title', 'attach', 'app', 'created', 'object-type', 'uri', 'guid',
+ 'author-name', 'author-link', 'author-avatar', 'plink'];
$condition = ['guid' => $guid, 'visible' => true, 'deleted' => false, 'private' => [Item::PUBLIC, Item::UNLISTED]];
$item = Item::selectFirst($fields, $condition);
$item["body"] = self::replacePeopleGuid($item["body"], $item["author-link"]);
// Add OEmbed and other information to the body
- $item["body"] = add_page_info_to_body($item["body"], false, true);
+ $item["body"] = PageInfo::searchAndAppendToBody($item["body"], false, true);
return $item;
} else {
}
if ($stored) {
- $fields = ['body', 'title', 'attach', 'tag', 'app', 'created', 'object-type', 'uri', 'guid',
- 'author-name', 'author-link', 'author-avatar'];
+ $fields = ['body', 'title', 'attach', 'app', 'created', 'object-type', 'uri', 'guid',
+ 'author-name', 'author-link', 'author-avatar', 'plink'];
$condition = ['guid' => $guid, 'visible' => true, 'deleted' => false, 'private' => [Item::PUBLIC, Item::UNLISTED]];
$item = Item::selectFirst($fields, $condition);
return false;
}
- $orig_url = DI::baseUrl()."/display/".$original_item["guid"];
-
$datarray = [];
$datarray["uid"] = $importer["uid"];
/// @todo Copy tag data from original post
- $prefix = share_header(
+ $prefix = BBCode::getShareOpeningTag(
$original_item["author-name"],
$original_item["author-link"],
$original_item["author-avatar"],
- $original_item["guid"],
+ $original_item["plink"],
$original_item["created"],
- $orig_url
+ $original_item["guid"]
);
if (!empty($original_item['title'])) {
Tag::storeFromBody($datarray['uri-id'], $datarray["body"]);
- $datarray["tag"] = $original_item["tag"];
$datarray["attach"] = $original_item["attach"];
$datarray["app"] = $original_item["app"];
}
// Fetch the parent item
- $parent = Item::selectFirst(['author-link'], ['id' => $item["parent"]]);
+ $parent = Item::selectFirst(['author-link'], ['id' => $item['parent']]);
// Only delete it if the parent author really fits
if (!Strings::compareLink($parent["author-link"], $contact["url"]) && !Strings::compareLink($item["author-link"], $contact["url"])) {
Item::markForDeletion(['id' => $item['id']]);
- Logger::log("Deleted target ".$target_guid." (".$item["id"].") from user ".$item["uid"]." parent: ".$item["parent"], Logger::DEBUG);
+ Logger::log("Deleted target ".$target_guid." (".$item["id"].") from user ".$item["uid"]." parent: ".$item['parent'], Logger::DEBUG);
}
return true;
// Add OEmbed and other information to the body
if (!self::isHubzilla($contact["url"])) {
- $body = add_page_info_to_body($body, false, true);
+ $body = PageInfo::searchAndAppendToBody($body, false, true);
}
}
$json = json_encode(["iv" => $b_iv, "key" => $b_aes_key]);
$encrypted_key_bundle = "";
- openssl_public_encrypt($json, $encrypted_key_bundle, $pubkey);
+ if (!@openssl_public_encrypt($json, $encrypted_key_bundle, $pubkey)) {
+ return false;
+ }
$json_object = json_encode(
["aes_key" => base64_encode($encrypted_key_bundle),
if (!intval(DI::config()->get("system", "diaspora_test"))) {
$content_type = (($public_batch) ? "application/magic-envelope+xml" : "application/json");
- $postResult = Network::post($dest_url."/", $envelope, ["Content-Type: ".$content_type]);
+ $postResult = DI::httpRequest()->post($dest_url . "/", $envelope, ["Content-Type: " . $content_type]);
$return_code = $postResult->getReturnCode();
} else {
Logger::log("test_mode");
"profile" => $profile,
"signature" => $signature];
- Logger::log("Send account migration ".print_r($message, true), Logger::DEBUG);
+ Logger::info('Send account migration', ['msg' => $message]);
return self::buildAndTransmit($owner, $contact, "account_migration", $message);
}
"following" => "true",
"sharing" => "true"];
- Logger::log("Send share ".print_r($message, true), Logger::DEBUG);
+ Logger::info('Send share', ['msg' => $message]);
return self::buildAndTransmit($owner, $contact, "contact", $message);
}
"following" => "false",
"sharing" => "false"];
- Logger::log("Send unshare ".print_r($message, true), Logger::DEBUG);
+ Logger::info('Send unshare', ['msg' => $message]);
return self::buildAndTransmit($owner, $contact, "contact", $message);
}
if ($item['author-link'] != $item['owner-link']) {
require_once 'mod/share.php';
- $body = share_header($item['author-name'], $item['author-link'], $item['author-avatar'],
- "", $item['created'], $item['plink']) . $body . '[/share]';
+ $body = BBCode::getShareOpeningTag($item['author-name'], $item['author-link'], $item['author-avatar'],
+ $item['plink'], $item['created']) . $body . '[/share]';
}
// convert to markdown
private static function prependParentAuthorMention($body, $profile_url)
{
- $profile = Contact::getDetailsByURL($profile_url);
+ $profile = Contact::getByURL($profile_url, false, ['addr', 'name', 'contact-type']);
if (!empty($profile['addr'])
&& $profile['contact-type'] != Contact::TYPE_COMMUNITY
&& !strstr($body, $profile['addr'])
return $result;
}
- $toplevel_item = Item::selectFirst(['guid', 'author-id', 'author-link'], ['id' => $item["parent"], 'parent' => $item["parent"]]);
+ $toplevel_item = Item::selectFirst(['guid', 'author-id', 'author-link'], ['id' => $item['parent'], 'parent' => $item['parent']]);
if (!DBA::isResult($toplevel_item)) {
- Logger::error('Missing parent conversation item', ['parent' => $item["parent"]]);
+ Logger::error('Missing parent conversation item', ['parent' => $item['parent']]);
return false;
}
$message["parent_author_signature"] = self::signature($owner, $message);
- Logger::log("Relayed data ".print_r($message, true), Logger::DEBUG);
+ Logger::info('Relayed data', ['msg' => $message]);
return self::buildAndTransmit($owner, $contact, $type, $message, $public_batch, $item["guid"]);
}
$msg_type = "retraction";
- if ($item['id'] == $item['parent']) {
+ if ($item['gravity'] == GRAVITY_PARENT) {
$target_type = "Post";
} elseif (in_array($item["verb"], [Activity::LIKE, Activity::DISLIKE])) {
$target_type = "Like";
"target_guid" => $item['guid'],
"target_type" => $target_type];
- Logger::log("Got message ".print_r($message, true), Logger::DEBUG);
+ Logger::info('Got message', ['msg' => $message]);
return self::buildAndTransmit($owner, $contact, $msg_type, $message, $public_batch, $item["guid"]);
}
{
$owner = User::getOwnerDataById($uid);
if (empty($owner)) {
- Logger::log("No owner post, so not storing signature", Logger::DEBUG);
+ Logger::info('No owner post, so not storing signature');
return false;
}
{
$owner = User::getOwnerDataById($uid);
if (empty($owner)) {
- Logger::log("No owner post, so not storing signature", Logger::DEBUG);
+ Logger::info('No owner post, so not storing signature');
return false;
}