X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=include%2Fdiaspora.php;h=2ff7a90cfbe677cc4eb92a880a4fb7be39bfb09d;hb=247f866655440bac8f3cd8b1eecdaeb08400ae3a;hp=c888959d716499909c439e6edb28ea84f88aa330;hpb=16b92af71f0d838f7859869461f1acea25329d95;p=friendica.git diff --git a/include/diaspora.php b/include/diaspora.php index c888959d71..2ff7a90cfb 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -2,41 +2,6 @@ /** * @file include/diaspora.php * @brief The implementation of the diaspora protocol - * - * Checklist: - * - * Checked: - * - send status - * - send comment - * - send like - * - send mail - * - send status retraction - * - send comment retraction on own post - * - send like retraction on own post - * - send comment retraction on diaspora post - * - send like retraction on diaspora post - * - receive status - * - receive reshare - * - receive comment - * - receive like - * - receive connect request - * - receive profile data - * - receive mail - * - receive comment retraction - * - receive like retraction - * - relay comment - * - relay like - * - relay comment retraction from diaspora - * - relay comment retraction from friendica - * - relay like retraction from diaspora - * - relay like retraction from friendica - * - send share - * - * Should work: - * - receive account deletion - * - send unshare - * - * Unchecked: */ require_once("include/items.php"); @@ -56,6 +21,13 @@ require_once("include/queue_fn.php"); */ class diaspora { + /** + * @brief Return a list of relay servers + * + * This is an experimental Diaspora feature. + * + * @return array of relay servers + */ public static function relay_list() { $serverdata = get_config("system", "relay_server"); @@ -100,7 +72,18 @@ class diaspora { return $relay; } - function repair_signature($signature, $handle = "", $level = 1) { + /** + * @brief 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 + */ + private function repair_signature($signature, $handle = "", $level = 1) { if ($signature == "") return ($signature); @@ -120,7 +103,7 @@ class diaspora { /** * @brief: Decodes incoming Diaspora message * - * @param array $importer from user table + * @param array $importer Array of the importer user * @param string $xml urldecoded Diaspora salmon * * @return array @@ -128,7 +111,7 @@ class diaspora { * 'author' -> author diaspora handle * 'key' -> author public key (converted to pkcs#8) */ - function decode($importer, $xml) { + public static function decode($importer, $xml) { $public = false; $basedom = parse_xml_string($xml); @@ -161,16 +144,6 @@ class diaspora { $decrypted = pkcs5_unpad($decrypted); - /** - * $decrypted now contains something like - * - * - * 8e+G2+ET8l5BPuW0sVTnQw== - * UvSMb4puPeB14STkcDWq+4QE302Edu15oaprAQSkLKU= - * galaxor@diaspora.priateship.org - * - */ - logger('decrypted: '.$decrypted, LOGGER_DEBUG); $idom = parse_xml_string($decrypted,false); @@ -269,7 +242,7 @@ class diaspora { * * @param array $msg The post that will be dispatched * - * @return bool Was the message accepted? + * @return int The message id of the generated message, "true" or "false" if there was an error */ public static function dispatch_public($msg) { @@ -281,7 +254,7 @@ class diaspora { // Use a dummy importer to import the data for the public copy $importer = array("uid" => 0, "page-flags" => PAGE_FREELOVE); - $item_id = self::dispatch($importer,$msg); + $message_id = self::dispatch($importer,$msg); // Now distribute it to the followers $r = q("SELECT `user`.* FROM `user` WHERE `user`.`uid` IN @@ -298,7 +271,7 @@ class diaspora { } else logger("No subscribers for ".$msg["author"]." ".print_r($msg, true)); - return $item_id; + return $message_id; } /** @@ -307,7 +280,7 @@ class diaspora { * @param array $importer Array of the importer user * @param array $msg The post that will be dispatched * - * @return bool Was the message accepted? + * @return int The message id of the generated message, "true" or "false" if there was an error */ public static function dispatch($importer, $msg) { @@ -331,6 +304,9 @@ class diaspora { case "comment": return self::receive_comment($importer, $sender, $fields, $msg["message"]); + case "contact": + return self::receive_contact_request($importer, $fields); + case "conversation": return self::receive_conversation($importer, $msg, $fields); @@ -352,9 +328,6 @@ class diaspora { case "profile": return self::receive_profile($importer, $fields); - case "request": - return self::receive_request($importer, $fields); - case "reshare": return self::receive_reshare($importer, $fields, $msg["message"]); @@ -410,6 +383,9 @@ class diaspora { if (in_array($type, array("signed_retraction", "relayable_retraction"))) $type = "retraction"; + if ($type == "request") + $type = "contact"; + $fields = new SimpleXMLElement("<".$type."/>"); $signed_data = ""; @@ -610,6 +586,14 @@ class diaspora { return $r; } + /** + * @brief get a handle (user@domain.tld) from a given contact id or gcontact id + * + * @param int $contact_id The id in the contact table + * @param int $gcontact_id The id in the gcontact table + * + * @return string the handle + */ public static function handle_from_contact($contact_id, $gcontact_id = 0) { $handle = False; @@ -631,7 +615,7 @@ class diaspora { if($contact['addr'] != "") $handle = $contact['addr']; - elseif(($contact['network'] === NETWORK_DFRN) || ($contact['self'] == 1)) { + else { $baseurl_start = strpos($contact['url'],'://') + 3; $baseurl_length = strpos($contact['url'],'/profile') - $baseurl_start; // allows installations in a subdirectory--not sure how Diaspora will handle $baseurl = substr($contact['url'], $baseurl_start, $baseurl_length); @@ -642,6 +626,14 @@ class diaspora { return $handle; } + /** + * @brief Get a contact id for a given handle + * + * @param int $uid The user id + * @param string $handle The handle in the format user@domain.tld + * + * @return The contact id + */ private function contact_by_handle($uid, $handle) { $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `addr` = '%s' LIMIT 1", intval($uid), @@ -664,6 +656,15 @@ class diaspora { return false; } + /** + * @brief Check if posting is allowed for this contact + * + * @param array $importer Array of the importer user + * @param array $contact The contact that is checked + * @param bool $is_comment Is the check for a comment? + * + * @return bool is the contact allowed to post? + */ private function post_allow($importer, $contact, $is_comment = false) { // perhaps we were already sharing with this person. Now they're sharing with us. @@ -694,6 +695,15 @@ class diaspora { return false; } + /** + * @brief Fetches the contact id for a handle and checks if posting is allowed + * + * @param array $importer Array of the importer user + * @param string $handle The checked handle in the format user@domain.tld + * @param bool $is_comment Is the check for a comment? + * + * @return array The contact data + */ private function allowed_contact_by_handle($importer, $handle, $is_comment = false) { $contact = self::contact_by_handle($importer["uid"], $handle); if (!$contact) { @@ -708,6 +718,14 @@ class diaspora { return $contact; } + /** + * @brief Does the message already exists on the system? + * + * @param int $uid The user id + * @param string $guid The guid of the message + * + * @return int|bool message id if the message already was stored into the system - or false. + */ private function message_exists($uid, $guid) { $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", intval($uid), @@ -716,12 +734,17 @@ class diaspora { if($r) { logger("message ".$guid." already exists for user ".$uid); - return true; + return $r[0]["id"]; } return false; } + /** + * @brief Checks for links to posts in a message + * + * @param array $item The item array + */ private function fetch_guid($item) { preg_replace_callback("&\[url=/posts/([^\[\]]*)\](.*)\[\/url\]&Usi", function ($match) use ($item){ @@ -729,11 +752,26 @@ class diaspora { },$item["body"]); } + /** + * @brief sub function of "fetch_guid" which checks for links in messages + * + * @param array $match array containing a link that has to be checked for a message link + * @param array $item The item array + */ private function fetch_guid_sub($match, $item) { if (!self::store_by_guid($match[1], $item["author-link"])) self::store_by_guid($match[1], $item["owner-link"]); } + /** + * @brief Fetches an item with a given guid from a given server + * + * @param string $guid the message guid + * @param string $server The server address + * @param int $uid The user id of the user + * + * @return int the message id of the stored message or false + */ private function store_by_guid($guid, $server, $uid = 0) { $serverparts = parse_url($server); $server = $serverparts["scheme"]."://".$serverparts["host"]; @@ -751,6 +789,18 @@ class diaspora { return self::dispatch_public($msg); } + /** + * @brief Fetches a message from a server + * + * @param string $guid message guid + * @param string $server The url of the server + * @param int $level Endless loop prevention + * + * @return array + * 'message' => The message XML + * 'author' => The author handle + * 'key' => The public key of the author + */ private function message($guid, $server, $level = 0) { if ($level > 5) @@ -794,6 +844,16 @@ class diaspora { return $msg; } + /** + * @brief Fetches the item record of a given guid + * + * @param int $uid The user id + * @param string $guid message guid + * @param string $author The handle of the item + * @param array $contact The contact of the item owner + * + * @return array the item record + */ private function parent_item($uid, $guid, $author, $contact) { $r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `origin`, `author-name`, `author-link`, `author-avatar`, @@ -829,6 +889,17 @@ class diaspora { } } + /** + * @brief returns contact details + * + * @param array $contact The default contact if the person isn't found + * @param array $person The record of the person + * @param int $uid The user id + * + * @return array + * 'cid' => contact id + * 'network' => network type + */ private function author_contact_by_url($contact, $person, $uid) { $r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1", @@ -844,10 +915,25 @@ class diaspora { return (array("cid" => $cid, "network" => $network)); } + /** + * @brief Is the profile a hubzilla profile? + * + * @param string $url The profile link + * + * @return bool is it a hubzilla server? + */ public static function is_redmatrix($url) { return(strstr($url, "/channel/")); } + /** + * @brief Generate a post link with a given handle and message guid + * + * @param string $addr The user handle + * @param string $guid message guid + * + * @return string the post link + */ private function plink($addr, $guid) { $r = q("SELECT `url`, `nick`, `network` FROM `fcontact` WHERE `addr`='%s' LIMIT 1", dbesc($addr)); @@ -870,7 +956,18 @@ class diaspora { return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid; } + /** + * @brief Processes an account deletion + * + * @param array $importer Array of the importer user + * @param object $data The message object + * + * @return bool Success + */ private function receive_account_deletion($importer, $data) { + + /// @todo Account deletion should remove the contact from the global contacts as well + $author = notags(unxmlify($data->author)); $contact = self::contact_by_handle($importer["uid"], $author); @@ -884,6 +981,16 @@ class diaspora { return true; } + /** + * @brief Processes an incoming comment + * + * @param array $importer Array of the importer user + * @param string $sender The sender of the message + * @param object $data The message object + * @param string $xml The original XML of the message + * + * @return int The message id of the generated comment or "false" if there was an error + */ private function receive_comment($importer, $sender, $data, $xml) { $guid = notags(unxmlify($data->guid)); $parent_guid = notags(unxmlify($data->parent_guid)); @@ -894,8 +1001,9 @@ class diaspora { if (!$contact) return false; - if (self::message_exists($importer["uid"], $guid)) - return false; + $message_id = self::message_exists($importer["uid"], $guid); + if ($message_id) + return $message_id; $parent_item = self::parent_item($importer["uid"], $parent_guid, $author, $contact); if (!$parent_item) @@ -961,6 +1069,18 @@ class diaspora { return $message_id; } + /** + * @brief processes and stores private messages + * + * @param array $importer Array of the importer user + * @param array $contact The contact of the message + * @param object $data The message object + * @param array $msg Array of the processed message, author handle and key + * @param object $mesg The private message + * @param array $conversation The conversation record to which this message belongs + * + * @return bool "true" if it was successful + */ private function receive_conversation_message($importer, $contact, $data, $msg, $mesg, $conversation) { $guid = notags(unxmlify($data->guid)); $subject = notags(unxmlify($data->subject)); @@ -1075,8 +1195,18 @@ class diaspora { "verb" => ACTIVITY_POST, "otype" => "mail" )); + return true; } + /** + * @brief Processes new private messages (answers to private messages are processed elsewhere) + * + * @param array $importer Array of the importer user + * @param array $msg Array of the processed message, author handle and key + * @param object $data The message object + * + * @return bool Success + */ private function receive_conversation($importer, $msg, $data) { $guid = notags(unxmlify($data->guid)); $subject = notags(unxmlify($data->subject)); @@ -1134,6 +1264,15 @@ class diaspora { return true; } + /** + * @brief Creates the body for a "like" message + * + * @param array $contact The contact that send us the "like" + * @param array $parent_item The item array of the parent item + * @param string $guid message guid + * + * @return string the body + */ private function construct_like_body($contact, $parent_item, $guid) { $bodyverb = t('%1$s likes %2$s\'s %3$s'); @@ -1144,6 +1283,14 @@ class diaspora { return sprintf($bodyverb, $ulink, $alink, $plink); } + /** + * @brief Creates a XML object for a "like" + * + * @param array $importer Array of the importer user + * @param array $parent_item The item array of the parent item + * + * @return string The XML + */ private function construct_like_object($importer, $parent_item) { $objtype = ACTIVITY_OBJ_NOTE; $link = ''; @@ -1159,6 +1306,15 @@ class diaspora { return xml::from_array($xmldata, $xml, true); } + /** + * @brief Processes "like" messages + * + * @param array $importer Array of the importer user + * @param string $sender The sender of the message + * @param object $data The message object + * + * @return int The message id of the generated like or "false" if there was an error + */ private function receive_like($importer, $sender, $data) { $positive = notags(unxmlify($data->positive)); $guid = notags(unxmlify($data->guid)); @@ -1175,8 +1331,9 @@ class diaspora { if (!$contact) return false; - if (self::message_exists($importer["uid"], $guid)) - return false; + $message_id = self::message_exists($importer["uid"], $guid); + if ($message_id) + return $message_id; $parent_item = self::parent_item($importer["uid"], $parent_guid, $author, $contact); if (!$parent_item) @@ -1193,7 +1350,7 @@ class diaspora { // "positive" = "false" would be a Dislike - wich isn't currently supported by Diaspora // We would accept this anyhow. - if ($positive === "true") + if ($positive == "true") $verb = ACTIVITY_LIKE; else $verb = ACTIVITY_DISLIKE; @@ -1247,6 +1404,14 @@ class diaspora { return $message_id; } + /** + * @brief Processes private messages + * + * @param array $importer Array of the importer user + * @param object $data The message object + * + * @return bool Success? + */ private function receive_message($importer, $data) { $guid = notags(unxmlify($data->guid)); $parent_guid = notags(unxmlify($data->parent_guid)); @@ -1318,27 +1483,59 @@ class diaspora { return true; } + /** + * @brief Processes participations - unsupported by now + * + * @param array $importer Array of the importer user + * @param object $data The message object + * + * @return bool always true + */ private function receive_participation($importer, $data) { // I'm not sure if we can fully support this message type return true; } + /** + * @brief Processes photos - unneeded + * + * @param array $importer Array of the importer user + * @param object $data The message object + * + * @return bool always true + */ private function receive_photo($importer, $data) { // There doesn't seem to be a reason for this function, since the photo data is transmitted in the status message as well return true; } + /** + * @brief Processes poll participations - unssupported + * + * @param array $importer Array of the importer user + * @param object $data The message object + * + * @return bool always true + */ private function receive_poll_participation($importer, $data) { // We don't support polls by now return true; } + /** + * @brief Processes incoming profile updates + * + * @param array $importer Array of the importer user + * @param object $data The message object + * + * @return bool Success + */ private function receive_profile($importer, $data) { $author = notags(unxmlify($data->author)); $contact = self::contact_by_handle($importer["uid"], $author); if (!$contact) - return; + return false; $name = unxmlify($data->first_name).((strlen($data->last_name)) ? " ".unxmlify($data->last_name) : ""); $image_url = unxmlify($data->image_url); @@ -1418,6 +1615,12 @@ class diaspora { return true; } + /** + * @brief Processes incoming friend requests + * + * @param array $importer Array of the importer user + * @param array $contact The contact that send the request + */ private function receive_request_make_friend($importer, $contact) { $a = get_app(); @@ -1464,11 +1667,8 @@ class diaspora { $BPhoto = "[url=".$contact["url"]."][img]".$contact["thumb"]."[/img][/url]"; $arr["body"] = sprintf(t("%1$s is now friends with %2$s"), $A, $B)."\n\n\n".$Bphoto; - $arr["object"] = "".ACTIVITY_OBJ_PERSON."".$contact["name"]."" - ."".$contact["url"]."/".$contact["name"].""; - $arr["object"] .= "".xmlify(''."\n"); - $arr["object"] .= xmlify(''."\n"); - $arr["object"] .= "\n"; + $arr["object"] = self::construct_new_friend_object($contact); + $arr["last-child"] = 1; $arr["allow_cid"] = $user[0]["allow_cid"]; @@ -1479,28 +1679,75 @@ class diaspora { $i = item_store($arr); if($i) proc_run("php", "include/notifier.php", "activity", $i); - } - } } - private function receive_request($importer, $data) { + /** + * @brief Creates a XML object for a "new friend" message + * + * @param array $contact Array of the contact + * + * @return string The XML + */ + private function construct_new_friend_object($contact) { + $objtype = ACTIVITY_OBJ_PERSON; + $link = ''."\n". + ''."\n"; + + $xmldata = array("object" => array("type" => $objtype, + "title" => $contact["name"], + "id" => $contact["url"]."/".$contact["name"], + "link" => $link)); + + return xml::from_array($xmldata, $xml, true); + } + + /** + * @brief Processes incoming sharing notification + * + * @param array $importer Array of the importer user + * @param object $data The message object + * + * @return bool Success + */ + private function receive_contact_request($importer, $data) { $author = unxmlify($data->author); $recipient = unxmlify($data->recipient); if (!$author || !$recipient) - return; + return false; - $contact = self::contact_by_handle($importer["uid"],$author); + // the current protocol version doesn't know these fields + // That means that we will assume their existance + if (isset($data->following)) + $following = (unxmlify($data->following) == "true"); + else + $following = true; - if($contact) { + if (isset($data->sharing)) + $sharing = (unxmlify($data->sharing) == "true"); + else + $sharing = true; - // perhaps we were already sharing with this person. Now they're sharing with us. - // That makes us friends. + $contact = self::contact_by_handle($importer["uid"],$author); - self::receive_request_make_friend($importer, $contact); - return true; + // perhaps we were already sharing with this person. Now they're sharing with us. + // That makes us friends. + if ($contact) { + if ($following AND $sharing) { + self::receive_request_make_friend($importer, $contact); + return true; + } else /// @todo Handle all possible variations of adding and retracting of permissions + return false; + } + + if (!$following AND $sharing AND in_array($importer["page-flags"], array(PAGE_SOAPBOX, PAGE_NORMAL))) { + logger("Author ".$author." wants to share with us - but doesn't want to listen. Request is ignored.", LOGGER_DEBUG); + return false; + } elseif (!$following AND !$sharing) { + logger("Author ".$author." doesn't want anything - and we don't know the author. Request is ignored.", LOGGER_DEBUG); + return false; } $ret = self::person_by_handle($author); @@ -1540,12 +1787,10 @@ class diaspora { return; } - $g = q("SELECT `def_gid` FROM `user` WHERE `uid` = %d LIMIT 1", - intval($importer["uid"]) - ); + $def_gid = get_default_group($importer['uid'], $ret["network"]); - if($g && intval($g[0]["def_gid"])) - group_add_member($importer["uid"], "", $contact_record["id"], $g[0]["def_gid"]); + if(intval($def_gid)) + group_add_member($importer["uid"], "", $contact_record["id"], $def_gid); if($importer["page-flags"] == PAGE_NORMAL) { @@ -1571,8 +1816,10 @@ class diaspora { // but if our page-type is PAGE_COMMUNITY or PAGE_SOAPBOX // we are going to change the relationship and make them a follower. - if($importer["page-flags"] == PAGE_FREELOVE) + if (($importer["page-flags"] == PAGE_FREELOVE) AND $sharing AND $following) $new_relation = CONTACT_IS_FRIEND; + elseif (($importer["page-flags"] == PAGE_FREELOVE) AND $sharing) + $new_relation = CONTACT_IS_SHARING; else $new_relation = CONTACT_IS_FOLLOWER; @@ -1598,6 +1845,15 @@ class diaspora { return true; } + /** + * @brief Fetches a message with a given guid + * + * @param string $guid message guid + * @param string $orig_author handle of the original post + * @param string $author handle of the sharer + * + * @return array The fetched item + */ private function original_item($guid, $orig_author, $author) { // Do we already have this item? @@ -1610,10 +1866,18 @@ class diaspora { logger("reshared message ".$guid." already exists on system."); // Maybe it is already a reshared item? - // Then refetch the content, since there can be many side effects with reshared posts from other networks or reshares from reshares - if (self::is_reshare($r[0]["body"], false)) + // Then refetch the content, if it is a reshare from a reshare. + // If it is a reshared post from another network then reformat to avoid display problems with two share elements + if (self::is_reshare($r[0]["body"], true)) $r = array(); - else + elseif (self::is_reshare($r[0]["body"], false)) { + $r[0]["body"] = diaspora2bb(bb2diaspora($r[0]["body"])); + + // Add OEmbed and other information to the body + $r[0]["body"] = add_page_info_to_body($r[0]["body"], false, true); + + return $r[0]; + } else return $r[0]; } @@ -1646,14 +1910,28 @@ class diaspora { FROM `item` WHERE `id` = %d AND `visible` AND NOT `deleted` AND `body` != '' LIMIT 1", intval($item_id)); - if ($r) + if ($r) { + // If it is a reshared post from another network then reformat to avoid display problems with two share elements + if (self::is_reshare($r[0]["body"], false)) + $r[0]["body"] = diaspora2bb(bb2diaspora($r[0]["body"])); + return $r[0]; + } } } return false; } + /** + * @brief Processes a reshare message + * + * @param array $importer Array of the importer user + * @param object $data The message object + * @param string $xml The original XML of the message + * + * @return int the message id + */ private function receive_reshare($importer, $data, $xml) { $root_author = notags(unxmlify($data->root_author)); $root_guid = notags(unxmlify($data->root_guid)); @@ -1666,8 +1944,9 @@ class diaspora { if (!$contact) return false; - if (self::message_exists($importer["uid"], $guid)) - return false; + $message_id = self::message_exists($importer["uid"], $guid); + if ($message_id) + return $message_id; $original_item = self::original_item($root_guid, $root_author, $author); if (!$original_item) @@ -1719,6 +1998,15 @@ class diaspora { return $message_id; } + /** + * @brief Processes retractions + * + * @param array $importer Array of the importer user + * @param array $contact The contact of the item owner + * @param object $data The message object + * + * @return bool success + */ private function item_retraction($importer, $contact, $data) { $target_type = notags(unxmlify($data->target_type)); $target_guid = notags(unxmlify($data->target_guid)); @@ -1768,8 +2056,19 @@ class diaspora { // notify others proc_run("php", "include/notifier.php", "drop", $r[0]["id"]); } + + return true; } + /** + * @brief Receives retraction messages + * + * @param array $importer Array of the importer user + * @param string $sender The sender of the message + * @param object $data The message object + * + * @return bool Success + */ private function receive_retraction($importer, $sender, $data) { $target_type = notags(unxmlify($data->target_type)); @@ -1789,10 +2088,11 @@ class diaspora { case "StatusMessage": return self::item_retraction($importer, $contact, $data);; + case "Contact": case "Person": /// @todo What should we do with an "unshare"? // Removing the contact isn't correct since we still can read the public items - //contact_remove($contact["id"]); + contact_remove($contact["id"]); return true; default: @@ -1802,6 +2102,15 @@ class diaspora { return true; } + /** + * @brief Receives status messages + * + * @param array $importer Array of the importer user + * @param object $data The message object + * @param string $xml The original XML of the message + * + * @return int The message id of the newly created item + */ private function receive_status_message($importer, $data, $xml) { $raw_message = unxmlify($data->raw_message); @@ -1821,8 +2130,9 @@ class diaspora { if (!$contact) return false; - if (self::message_exists($importer["uid"], $guid)) - return false; + $message_id = self::message_exists($importer["uid"], $guid); + if ($message_id) + return $message_id; $address = array(); if ($data->location) @@ -1833,6 +2143,7 @@ class diaspora { $datarray = array(); + // Attach embedded pictures to the body if ($data->photo) { foreach ($data->photo AS $photo) $body = "[img]".unxmlify($photo->remote_photo_path). @@ -1891,19 +2202,42 @@ class diaspora { return $message_id; } - /****************************************************************************************** + /* ************************************************************************************** * * Here are all the functions that are needed to transmit data with the Diaspora protocol * - ******************************************************************************************/ + * ************************************************************************************** */ - private function my_handle($me) { + /** + * @brief returnes the handle of a contact + * + * @param array $me contact array + * + * @return string the handle in the format user@domain.tld + */ + private function my_handle($contact) { if ($contact["addr"] != "") return $contact["addr"]; // Normally we should have a filled "addr" field - but in the past this wasn't the case // So - just in case - we build the the address here. - return $me["nickname"]."@".substr(App::get_baseurl(), strpos(App::get_baseurl(),"://") + 3); + if ($contact["nickname"] != "") + $nick = $contact["nickname"]; + else + $nick = $contact["nick"]; + + return $nick."@".substr(App::get_baseurl(), strpos(App::get_baseurl(),"://") + 3); } + /** + * @brief Creates the envelope for a public message + * + * @param string $msg The message that is to be transmitted + * @param array $user The record of the sender + * @param array $contact Target of the communication + * @param string $prvkey The private key of the sender + * @param string $pubkey The public key of the receiver + * + * @return string The envelope + */ private function build_public_message($msg, $user, $contact, $prvkey, $pubkey) { logger("Message: ".$msg, LOGGER_DATA); @@ -1939,6 +2273,17 @@ class diaspora { return $magic_env; } + /** + * @brief Creates the envelope for a private message + * + * @param string $msg The message that is to be transmitted + * @param array $user The record of the sender + * @param array $contact Target of the communication + * @param string $prvkey The private key of the sender + * @param string $pubkey The public key of the receiver + * + * @return string The envelope + */ private function build_private_message($msg, $user, $contact, $prvkey, $pubkey) { logger("Message: ".$msg, LOGGER_DATA); @@ -2018,6 +2363,18 @@ class diaspora { return $magic_env; } + /** + * @brief Create the envelope for a message + * + * @param string $msg The message that is to be transmitted + * @param array $user The record of the sender + * @param array $contact Target of the communication + * @param string $prvkey The private key of the sender + * @param string $pubkey The public key of the receiver + * @param bool $public Is the message public? + * + * @return string The message that will be transmitted to other servers + */ private function build_message($msg, $user, $contact, $prvkey, $pubkey, $public = false) { if ($public) @@ -2030,6 +2387,14 @@ class diaspora { return $slap; } + /** + * @brief Creates a signature for a message + * + * @param array $owner the array of the owner of the message + * @param array $message The message that is to be signed + * + * @return string The signature + */ private function signature($owner, $message) { $sigmsg = $message; unset($sigmsg["author_signature"]); @@ -2040,6 +2405,18 @@ class diaspora { return base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); } + /** + * @brief Transmit a message to a target server + * + * @param array $owner the array of the item owner + * @param array $contact Target of the communication + * @param string $slap The message that is to be transmitted + * @param bool $public_batch Is it a public post? + * @param bool $queue_run Is the transmission called from the queue? + * @param string $guid message guid + * + * @return int Result of the transmission + */ public static function transmit($owner, $contact, $slap, $public_batch, $queue_run=false, $guid = "") { $a = get_app(); @@ -2092,6 +2469,19 @@ class diaspora { } + /** + * @brief Builds and transmit messages + * + * @param array $owner the array of the item owner + * @param array $contact Target of the communication + * @param string $type The message type + * @param array $message The message data + * @param bool $public_batch Is it a public post? + * @param string $guid message guid + * @param bool $spool Should the transmission be spooled or transmitted? + * + * @return int Result of the transmission + */ private function build_and_transmit($owner, $contact, $type, $message, $public_batch = false, $guid = "", $spool = false) { $data = array("XML" => array("post" => array($type => $message))); @@ -2114,6 +2504,14 @@ class diaspora { return $return_code; } + /** + * @brief Sends a "share" message + * + * @param array $owner the array of the item owner + * @param array $contact Target of the communication + * + * @return int The result of the transmission + */ public static function send_share($owner,$contact) { $message = array("sender_handle" => self::my_handle($owner), @@ -2122,6 +2520,14 @@ class diaspora { return self::build_and_transmit($owner, $contact, "request", $message); } + /** + * @brief sends an "unshare" + * + * @param array $owner the array of the item owner + * @param array $contact Target of the communication + * + * @return int The result of the transmission + */ public static function send_unshare($owner,$contact) { $message = array("post_guid" => $owner["guid"], @@ -2131,12 +2537,20 @@ class diaspora { return self::build_and_transmit($owner, $contact, "retraction", $message); } + /** + * @brief Checks a message body if it is a reshare + * + * @param string $body The message body that is to be check + * @param bool $complete Should it be a complete check or a simple check? + * + * @return array|bool Reshare details or "false" if no reshare + */ public static function is_reshare($body, $complete = true) { $body = trim($body); // Skip if it isn't a pure repeated messages // Does it start with a share? - if (strpos($body, "[share") > 0) + if ((strpos($body, "[share") > 0) AND $complete) return(false); // Does it end with a share? @@ -2197,11 +2611,22 @@ class diaspora { $link = $matches[1]; $ret["root_guid"] = preg_replace("=https?://(.*)/posts/(.*)=ism", "$2", $link); - if (($ret["root_guid"] == $link) OR ($ret["root_guid"] == "")) + if (($ret["root_guid"] == $link) OR (trim($ret["root_guid"]) == "")) return(false); + return($ret); } + /** + * @brief Sends a post + * + * @param array $item The item that will be exported + * @param array $owner the array of the item owner + * @param array $contact Target of the communication + * @param bool $public_batch Is it a public post? + * + * @return int The result of the transmission + */ public static function send_status($item, $owner, $contact, $public_batch = false) { $myaddr = self::my_handle($owner); @@ -2269,10 +2694,16 @@ class diaspora { return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); } + /** + * @brief Creates a "like" object + * + * @param array $item The item that will be exported + * @param array $owner the array of the item owner + * + * @return array The data for a "like" + */ private function construct_like($item, $owner) { - $myaddr = self::my_handle($owner); - $p = q("SELECT `guid`, `uri`, `parent-uri` FROM `item` WHERE `uri` = '%s' LIMIT 1", dbesc($item["thr-parent"])); if(!$p) @@ -2287,14 +2718,20 @@ class diaspora { "guid" => $item["guid"], "target_type" => $target_type, "parent_guid" => $parent["guid"], - "author_signature" => $authorsig, - "diaspora_handle" => $myaddr)); + "author_signature" => "", + "diaspora_handle" => self::my_handle($owner))); } + /** + * @brief Creates the object for a comment + * + * @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 + */ private function construct_comment($item, $owner) { - $myaddr = self::my_handle($owner); - $p = q("SELECT `guid` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1", intval($item["parent"]), intval($item["parent"]) @@ -2311,9 +2748,19 @@ class diaspora { "parent_guid" => $parent["guid"], "author_signature" => "", "text" => $text, - "diaspora_handle" => $myaddr)); + "diaspora_handle" => self::my_handle($owner))); } + /** + * @brief Send a like or a comment + * + * @param array $item The item that will be exported + * @param array $owner the array of the item owner + * @param array $contact Target of the communication + * @param bool $public_batch Is it a public post? + * + * @return int The result of the transmission + */ public static function send_followup($item,$owner,$contact,$public_batch = false) { if($item['verb'] === ACTIVITY_LIKE) { @@ -2332,7 +2779,15 @@ class diaspora { return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); } - private function message_from_signatur($item, $signature) { + /** + * @brief Creates a message from a signature record entry + * + * @param array $item The item that will be exported + * @param array $signature The entry of the "sign" record + * + * @return string The message + */ + private function message_from_signature($item, $signature) { // Split the signed text $signed_parts = explode(";", $signature['signed_text']); @@ -2374,6 +2829,16 @@ class diaspora { return $message; } + /** + * @brief Relays messages (like, comment, retraction) to other servers if we are the thread owner + * + * @param array $item The item that will be exported + * @param array $owner the array of the item owner + * @param array $contact Target of the communication + * @param bool $public_batch Is it a public post? + * + * @return int The result of the transmission + */ public static function send_relay($item, $owner, $contact, $public_batch = false) { if ($item["deleted"]) @@ -2400,7 +2865,7 @@ class diaspora { // Old way - is used by the internal Friendica functions /// @todo Change all signatur storing functions to the new format if ($signature['signed_text'] AND $signature['signature'] AND $signature['signer']) - $message = self::message_from_signatur($item, $signature); + $message = self::message_from_signature($item, $signature); else {// New way $msg = json_decode($signature['signed_text'], true); @@ -2427,10 +2892,20 @@ class diaspora { return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); } + /** + * @brief Sends a retraction (deletion) of a message, like or comment + * + * @param array $item The item that will be exported + * @param array $owner the array of the item owner + * @param array $contact Target of the communication + * @param bool $public_batch Is it a public post? + * @param bool $relay Is the retraction transmitted from a relay? + * + * @return int The result of the transmission + */ public static function send_retraction($item, $owner, $contact, $public_batch = false, $relay = false) { $itemaddr = self::handle_from_contact($item["contact-id"], $item["gcontact-id"]); - //$myaddr = self::my_handle($owner); // Check whether the retraction is for a top-level post or whether it's a relayable if ($item["uri"] !== $item["parent-uri"]) { @@ -2458,6 +2933,15 @@ class diaspora { return self::build_and_transmit($owner, $contact, $msg_type, $message, $public_batch, $item["guid"]); } + /** + * @brief Sends a mail + * + * @param array $item The item that will be exported + * @param array $owner The owner + * @param array $contact Target of the communication + * + * @return int The result of the transmission + */ public static function send_mail($item, $owner, $contact) { $myaddr = self::my_handle($owner); @@ -2515,6 +2999,11 @@ class diaspora { return self::build_and_transmit($owner, $contact, $type, $message, false, $item["guid"]); } + /** + * @brief Sends profile data + * + * @param int $uid The user id + */ public static function send_profile($uid) { if (!$uid) @@ -2592,5 +3081,106 @@ class diaspora { foreach($recips as $recip) self::build_and_transmit($profile, $recip, "profile", $message, false, "", true); } + + /** + * @brief Stores the signature for likes that are created on our system + * + * @param array $contact The contact array of the "like" + * @param int $post_id The post id of the "like" + * + * @return bool Success + */ + public static function store_like_signature($contact, $post_id) { + + // Is the contact the owner? Then fetch the private key + if (!$contact['self'] OR ($contact['uid'] == 0)) { + logger("No owner post, so not storing signature", LOGGER_DEBUG); + return false; + } + + $r = q("SELECT `prvkey` FROM `user` WHERE `uid` = %d LIMIT 1", intval($contact['uid'])); + if(!$r) + return false; + + $contact["uprvkey"] = $r[0]['prvkey']; + + $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1", intval($post_id)); + if (!$r) + return false; + + if (!in_array($r[0]["verb"], array(ACTIVITY_LIKE, ACTIVITY_DISLIKE))) + return false; + + $message = self::construct_like($r[0], $contact); + $message["author_signature"] = self::signature($contact, $message); + + // In the future we will store the signature more flexible to support new fields. + // Right now we cannot change this since old Friendica versions (prior to 3.5) can only handle this format. + // (We are transmitting this data here via DFRN) + + $signed_text = $message["positive"].";".$message["guid"].";".$message["target_type"].";". + $message["parent_guid"].";".$message["diaspora_handle"]; + + q("INSERT INTO `sign` (`iid`,`signed_text`,`signature`,`signer`) VALUES (%d,'%s','%s','%s')", + intval($post_id), + dbesc($signed_text), + dbesc($message["author_signature"]), + dbesc($message["diaspora_handle"]) + ); + + // This here will replace the lines above, once Diaspora changed its protocol + //q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')", + // intval($message_id), + // dbesc(json_encode($message)) + //); + + logger('Stored diaspora like signature'); + return true; + } + + /** + * @brief Stores the signature for comments that are created on our system + * + * @param array $item The item array of the comment + * @param array $contact The contact array of the item owner + * @param string $uprvkey The private key of the sender + * @param int $message_id The message id of the comment + * + * @return bool Success + */ + public static function store_comment_signature($item, $contact, $uprvkey, $message_id) { + + if ($uprvkey == "") { + logger('No private key, so not storing comment signature', LOGGER_DEBUG); + return false; + } + + $contact["uprvkey"] = $uprvkey; + + $message = self::construct_comment($item, $contact); + $message["author_signature"] = self::signature($contact, $message); + + // In the future we will store the signature more flexible to support new fields. + // Right now we cannot change this since old Friendica versions (prior to 3.5) can only handle this format. + // (We are transmitting this data here via DFRN) + $signed_text = $message["guid"].";".$message["parent_guid"].";". + $message["text"].";".$message["diaspora_handle"]; + + q("INSERT INTO `sign` (`iid`,`signed_text`,`signature`,`signer`) VALUES (%d,'%s','%s','%s')", + intval($message_id), + dbesc($signed_text), + dbesc($message["author_signature"]), + dbesc($message["diaspora_handle"]) + ); + + // This here will replace the lines above, once Diaspora changed its protocol + //q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')", + // intval($message_id), + // dbesc(json_encode($message)) + //); + + logger('Stored diaspora comment signature'); + return true; + } } ?>