]> git.mxchange.org Git - friendica.git/blob - include/diaspora2.php
7aa0fc6989abb0b1008b61dae054c9c3f6ec53e4
[friendica.git] / include / diaspora2.php
1 <?php
2 /**
3  * @file include/diaspora.php
4  * @brief The implementation of the diaspora protocol
5  */
6
7 require_once("include/items.php");
8 require_once("include/bb2diaspora.php");
9 require_once("include/Scrape.php");
10 require_once("include/Contact.php");
11 require_once("include/Photo.php");
12 require_once("include/socgraph.php");
13 require_once("include/group.php");
14 require_once("include/xml.php");
15 require_once("include/datetime.php");
16
17 /**
18  * @brief This class contain functions to create and send Diaspora XML files
19  *
20  */
21 class diaspora {
22
23         public static function fetch_relay() {
24
25                 $serverdata = get_config("system", "relay_server");
26                 if ($serverdata == "")
27                         return array();
28
29                 $relay = array();
30
31                 $servers = explode(",", $serverdata);
32
33                 foreach($servers AS $server) {
34                         $server = trim($server);
35                         $batch = $server."/receive/public";
36
37                         $relais = q("SELECT `batch`, `id`, `name`,`network` FROM `contact` WHERE `uid` = 0 AND `batch` = '%s' LIMIT 1", dbesc($batch));
38
39                         if (!$relais) {
40                                 $addr = "relay@".str_replace("http://", "", normalise_link($server));
41
42                                 $r = q("INSERT INTO `contact` (`uid`, `created`, `name`, `nick`, `addr`, `url`, `nurl`, `batch`, `network`, `rel`, `blocked`, `pending`, `writable`, `name-date`, `uri-date`, `avatar-date`)
43                                         VALUES (0, '%s', '%s', 'relay', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, '%s', '%s', '%s')",
44                                         datetime_convert(),
45                                         dbesc($addr),
46                                         dbesc($addr),
47                                         dbesc($server),
48                                         dbesc(normalise_link($server)),
49                                         dbesc($batch),
50                                         dbesc(NETWORK_DIASPORA),
51                                         intval(CONTACT_IS_FOLLOWER),
52                                         dbesc(datetime_convert()),
53                                         dbesc(datetime_convert()),
54                                         dbesc(datetime_convert())
55                                 );
56
57                                 $relais = q("SELECT `batch`, `id`, `name`,`network` FROM `contact` WHERE `uid` = 0 AND `batch` = '%s' LIMIT 1", dbesc($batch));
58                                 if ($relais)
59                                         $relay[] = $relais[0];
60                         } else
61                                 $relay[] = $relais[0];
62                 }
63
64                 return $relay;
65         }
66
67         /**
68          * @brief Dispatches public messages and find the fitting receivers
69          *
70          * @param array $msg The post that will be dispatched
71          *
72          * @return bool Was the message accepted?
73          */
74         public static function dispatch_public($msg) {
75
76                 $enabled = intval(get_config("system", "diaspora_enabled"));
77                 if (!$enabled) {
78                         logger("diaspora is disabled");
79                         return false;
80                 }
81
82                 // Use a dummy importer to import the data for the public copy
83                 $importer = array("uid" => 0, "page-flags" => PAGE_FREELOVE);
84                 $item_id = self::dispatch($importer,$msg);
85
86                 // Now distribute it to the followers
87                 $r = q("SELECT `user`.* FROM `user` WHERE `user`.`uid` IN
88                         (SELECT `contact`.`uid` FROM `contact` WHERE `contact`.`network` = '%s' AND `contact`.`addr` = '%s')
89                         AND NOT `account_expired` AND NOT `account_removed`",
90                         dbesc(NETWORK_DIASPORA),
91                         dbesc($msg["author"])
92                 );
93                 if($r) {
94                         foreach($r as $rr) {
95                                 logger("delivering to: ".$rr["username"]);
96                                 self::dispatch($rr,$msg);
97                         }
98                 } else
99                         logger("No subscribers for ".$msg["author"]." ".print_r($msg, true));
100
101                 return $item_id;
102         }
103
104         /**
105          * @brief Dispatches the different message types to the different functions
106          *
107          * @param array $importer Array of the importer user
108          * @param array $msg The post that will be dispatched
109          *
110          * @return bool Was the message accepted?
111          */
112         public static function dispatch($importer, $msg) {
113
114                 // The sender is the handle of the contact that sent the message.
115                 // This will often be different with relayed messages (for example "like" and "comment")
116                 $sender = $msg["author"];
117
118                 if (!diaspora::valid_posting($msg, $fields)) {
119                         logger("Invalid posting");
120                         return false;
121                 }
122
123                 $type = $fields->getName();
124
125                 switch ($type) {
126                         case "account_deletion": // Done
127                                 //return true;
128                                 return self::receive_account_deletion($importer, $fields);
129
130                         case "comment": // Done
131                                 //return true;
132                                 return self::receive_comment($importer, $sender, $fields);
133
134                         case "conversation": // Done
135                                 //return true;
136                                 return self::receive_conversation($importer, $msg, $fields);
137
138                         case "like": // Done
139                                 //return true;
140                                 return self::receive_like($importer, $sender, $fields);
141
142                         case "message": // Done
143                                 //return true;
144                                 return self::receive_message($importer, $fields);
145
146                         case "participation": // Not implemented
147                                 return self::receive_participation($importer, $fields);
148
149                         case "photo": // Not needed
150                                 return self::receive_photo($importer, $fields);
151
152                         case "poll_participation": // Not implemented
153                                 return self::receive_poll_participation($importer, $fields);
154
155                         case "profile": // Done
156                                 //return true;
157                                 return self::receive_profile($importer, $fields);
158
159                         case "request":
160                                 //return true;
161                                 return self::receive_request($importer, $fields);
162
163                         case "reshare": // Done
164                                 //return true;
165                                 return self::receive_reshare($importer, $fields);
166
167                         case "retraction": // Done
168                                 //return true;
169                                 return self::receive_retraction($importer, $sender, $fields);
170
171                         case "status_message": // Done
172                                 //return true;
173                                 return self::receive_status_message($importer, $fields);
174
175                         default:
176                                 logger("Unknown message type ".$type);
177                                 return false;
178                 }
179
180                 return true;
181         }
182
183         /**
184          * @brief Checks if a posting is valid and fetches the data fields.
185          *
186          * This function does not only check the signature.
187          * It also does the conversion between the old and the new diaspora format.
188          *
189          * @param array $msg Array with the XML, the sender handle and the sender signature
190          * @param object $fields SimpleXML object that contains the posting when it is valid
191          *
192          * @return bool Is the posting valid?
193          */
194         private function valid_posting($msg, &$fields) {
195
196                 $data = parse_xml_string($msg["message"], false);
197
198                 if (!is_object($data))
199                         return false;
200
201                 $first_child = $data->getName();
202
203                 // Is this the new or the old version?
204                 if ($data->getName() == "XML") {
205                         $oldXML = true;
206                         foreach ($data->post->children() as $child)
207                                 $element = $child;
208                 } else {
209                         $oldXML = false;
210                         $element = $data;
211                 }
212
213                 $type = $element->getName();
214                 $orig_type = $type;
215
216                 // All retractions are handled identically from now on.
217                 // In the new version there will only be "retraction".
218                 if (in_array($type, array("signed_retraction", "relayable_retraction")))
219                         $type = "retraction";
220
221                 $fields = new SimpleXMLElement("<".$type."/>");
222
223                 $signed_data = "";
224
225                 foreach ($element->children() AS $fieldname => $entry) {
226                         if ($oldXML) {
227                                 // Translation for the old XML structure
228                                 if ($fieldname == "diaspora_handle")
229                                         $fieldname = "author";
230
231                                 if ($fieldname == "participant_handles")
232                                         $fieldname = "participants";
233
234                                 if (in_array($type, array("like", "participation"))) {
235                                         if ($fieldname == "target_type")
236                                                 $fieldname = "parent_type";
237                                 }
238
239                                 if ($fieldname == "sender_handle")
240                                         $fieldname = "author";
241
242                                 if ($fieldname == "recipient_handle")
243                                         $fieldname = "recipient";
244
245                                 if ($fieldname == "root_diaspora_id")
246                                         $fieldname = "root_author";
247
248                                 if ($type == "retraction") {
249                                         if ($fieldname == "post_guid")
250                                                 $fieldname = "target_guid";
251
252                                         if ($fieldname == "type")
253                                                 $fieldname = "target_type";
254                                 }
255                         }
256
257                         if ($fieldname == "author_signature")
258                                 $author_signature = base64_decode($entry);
259                         elseif ($fieldname == "parent_author_signature")
260                                 $parent_author_signature = base64_decode($entry);
261                         elseif ($fieldname != "target_author_signature") {
262                                 if ($signed_data != "") {
263                                         $signed_data .= ";";
264                                         $signed_data_parent .= ";";
265                                 }
266
267                                 $signed_data .= $entry;
268                         }
269                         if (!in_array($fieldname, array("parent_author_signature", "target_author_signature")) OR
270                                 ($orig_type == "relayable_retraction"))
271                                 xml::copy($entry, $fields, $fieldname);
272                 }
273
274                 // This is something that shouldn't happen at all.
275                 if (in_array($type, array("status_message", "reshare", "profile")))
276                         if ($msg["author"] != $fields->author) {
277                                 logger("Message handle is not the same as envelope sender. Quitting this message.");
278                                 return false;
279                         }
280
281                 // Only some message types have signatures. So we quit here for the other types.
282                 if (!in_array($type, array("comment", "message", "like")))
283                         return true;
284
285                 // No author_signature? This is a must, so we quit.
286                 if (!isset($author_signature))
287                         return false;
288
289                 if (isset($parent_author_signature)) {
290                         $key = self::get_key($msg["author"]);
291
292                         if (!rsa_verify($signed_data, $parent_author_signature, $key, "sha256"))
293                                 return false;
294                 }
295
296                 $key = self::get_key($fields->author);
297
298                 return rsa_verify($signed_data, $author_signature, $key, "sha256");
299         }
300
301         /**
302          * @brief Fetches the public key for a given handle
303          *
304          * @param string $handle The handle
305          *
306          * @return string The public key
307          */
308         private function get_key($handle) {
309                 logger("Fetching diaspora key for: ".$handle);
310
311                 $r = self::get_person_by_handle($handle);
312                 if($r)
313                         return $r["pubkey"];
314
315                 return "";
316         }
317
318         /**
319          * @brief Fetches data for a given handle
320          *
321          * @param string $handle The handle
322          *
323          * @return array the queried data
324          */
325         private function get_person_by_handle($handle) {
326
327                 $r = q("SELECT * FROM `fcontact` WHERE `network` = '%s' AND `addr` = '%s' LIMIT 1",
328                         dbesc(NETWORK_DIASPORA),
329                         dbesc($handle)
330                 );
331                 if ($r) {
332                         $person = $r[0];
333                         logger("In cache ".print_r($r,true), LOGGER_DEBUG);
334
335                         // update record occasionally so it doesn't get stale
336                         $d = strtotime($person["updated"]." +00:00");
337                         if ($d < strtotime("now - 14 days"))
338                                 $update = true;
339                 }
340
341                 if (!$person OR $update) {
342                         logger("create or refresh", LOGGER_DEBUG);
343                         $r = probe_url($handle, PROBE_DIASPORA);
344
345                         // Note that Friendica contacts will return a "Diaspora person"
346                         // if Diaspora connectivity is enabled on their server
347                         if ($r AND ($r["network"] === NETWORK_DIASPORA)) {
348                                 self::add_fcontact($r, $update);
349                                 $person = $r;
350                         }
351                 }
352                 return $person;
353         }
354
355         /**
356          * @brief Updates the fcontact table
357          *
358          * @param array $arr The fcontact data
359          * @param bool $update Update or insert?
360          *
361          * @return string The id of the fcontact entry
362          */
363         private function add_fcontact($arr, $update = false) {
364                 /// @todo Remove this function from include/network.php
365
366                 if($update) {
367                         $r = q("UPDATE `fcontact` SET
368                                         `name` = '%s',
369                                         `photo` = '%s',
370                                         `request` = '%s',
371                                         `nick` = '%s',
372                                         `addr` = '%s',
373                                         `batch` = '%s',
374                                         `notify` = '%s',
375                                         `poll` = '%s',
376                                         `confirm` = '%s',
377                                         `alias` = '%s',
378                                         `pubkey` = '%s',
379                                         `updated` = '%s'
380                                 WHERE `url` = '%s' AND `network` = '%s'",
381                                         dbesc($arr["name"]),
382                                         dbesc($arr["photo"]),
383                                         dbesc($arr["request"]),
384                                         dbesc($arr["nick"]),
385                                         dbesc($arr["addr"]),
386                                         dbesc($arr["batch"]),
387                                         dbesc($arr["notify"]),
388                                         dbesc($arr["poll"]),
389                                         dbesc($arr["confirm"]),
390                                         dbesc($arr["alias"]),
391                                         dbesc($arr["pubkey"]),
392                                         dbesc(datetime_convert()),
393                                         dbesc($arr["url"]),
394                                         dbesc($arr["network"])
395                                 );
396                 } else {
397                         $r = q("INSERT INTO `fcontact` (`url`,`name`,`photo`,`request`,`nick`,`addr`,
398                                         `batch`, `notify`,`poll`,`confirm`,`network`,`alias`,`pubkey`,`updated`)
399                                 VALUES ('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')",
400                                         dbesc($arr["url"]),
401                                         dbesc($arr["name"]),
402                                         dbesc($arr["photo"]),
403                                         dbesc($arr["request"]),
404                                         dbesc($arr["nick"]),
405                                         dbesc($arr["addr"]),
406                                         dbesc($arr["batch"]),
407                                         dbesc($arr["notify"]),
408                                         dbesc($arr["poll"]),
409                                         dbesc($arr["confirm"]),
410                                         dbesc($arr["network"]),
411                                         dbesc($arr["alias"]),
412                                         dbesc($arr["pubkey"]),
413                                         dbesc(datetime_convert())
414                                 );
415                 }
416
417                 return $r;
418         }
419
420         private function get_contact_by_handle($uid, $handle) {
421                 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `addr` = '%s' LIMIT 1",
422                         intval($uid),
423                         dbesc($handle)
424                 );
425
426                 if ($r)
427                         return $r[0];
428
429                 $handle_parts = explode("@", $handle);
430                 $nurl_sql = "%%://".$handle_parts[1]."%%/profile/".$handle_parts[0];
431                 $r = q("SELECT * FROM `contact` WHERE `network` = '%s' AND `uid` = %d AND `nurl` LIKE '%s' LIMIT 1",
432                         dbesc(NETWORK_DFRN),
433                         intval($uid),
434                         dbesc($nurl_sql)
435                 );
436                 if($r)
437                         return $r[0];
438
439                 return false;
440         }
441
442         private function post_allow($importer, $contact, $is_comment = false) {
443
444                 // perhaps we were already sharing with this person. Now they're sharing with us.
445                 // That makes us friends.
446                 // Normally this should have handled by getting a request - but this could get lost
447                 if($contact["rel"] == CONTACT_IS_FOLLOWER && in_array($importer["page-flags"], array(PAGE_FREELOVE))) {
448                         q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
449                                 intval(CONTACT_IS_FRIEND),
450                                 intval($contact["id"]),
451                                 intval($importer["uid"])
452                         );
453                         $contact["rel"] = CONTACT_IS_FRIEND;
454                         logger("defining user ".$contact["nick"]." as friend");
455                 }
456
457                 if(($contact["blocked"]) || ($contact["readonly"]) || ($contact["archive"]))
458                         return false;
459                 if($contact["rel"] == CONTACT_IS_SHARING || $contact["rel"] == CONTACT_IS_FRIEND)
460                         return true;
461                 if($contact["rel"] == CONTACT_IS_FOLLOWER)
462                         if(($importer["page-flags"] == PAGE_COMMUNITY) OR $is_comment)
463                                 return true;
464
465                 // Messages for the global users are always accepted
466                 if ($importer["uid"] == 0)
467                         return true;
468
469                 return false;
470         }
471
472         private function get_allowed_contact_by_handle($importer, $handle, $is_comment = false) {
473                 $contact = self::get_contact_by_handle($importer["uid"], $handle);
474                 if (!$contact) {
475                         logger("A Contact for handle ".$handle." and user ".$importer["uid"]." was not found");
476                         return false;
477                 }
478
479                 if (!self::post_allow($importer, $contact, false)) {
480                         logger("The handle: ".$handle." is not allowed to post to user ".$importer["uid"]);
481                         return false;
482                 }
483                 return $contact;
484         }
485
486         private function message_exists($uid, $guid) {
487                 $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
488                         intval($uid),
489                         dbesc($guid)
490                 );
491
492                 if($r) {
493                         logger("message ".$guid." already exists for user ".$uid);
494                         return false;
495                 }
496
497                 return true;
498         }
499
500         private function fetch_guid($item) {
501                 preg_replace_callback("&\[url=/posts/([^\[\]]*)\](.*)\[\/url\]&Usi",
502                         function ($match) use ($item){
503                                 return(self::fetch_guid_sub($match, $item));
504                         },$item["body"]);
505         }
506
507         private function fetch_guid_sub($match, $item) {
508                 if (!self::store_by_guid($match[1], $item["author-link"]))
509                         self::store_by_guid($match[1], $item["owner-link"]);
510         }
511
512         private function store_by_guid($guid, $server, $uid = 0) {
513                 $serverparts = parse_url($server);
514                 $server = $serverparts["scheme"]."://".$serverparts["host"];
515
516                 logger("Trying to fetch item ".$guid." from ".$server, LOGGER_DEBUG);
517
518                 $msg = self::fetch_message($guid, $server);
519
520                 if (!$msg)
521                         return false;
522
523                 logger("Successfully fetched item ".$guid." from ".$server, LOGGER_DEBUG);
524
525                 // Now call the dispatcher
526                 return self::dispatch_public($msg);
527         }
528
529         private function fetch_message($guid, $server, $level = 0) {
530
531                 if ($level > 5)
532                         return false;
533
534                 // This will work for Diaspora and newer Friendica servers
535                 $source_url = $server."/p/".$guid.".xml";
536                 $x = fetch_url($source_url);
537                 if(!$x)
538                         return false;
539
540                 $source_xml = parse_xml_string($x, false);
541
542                 if (!is_object($source_xml))
543                         return false;
544
545                 if ($source_xml->post->reshare) {
546                         // Reshare of a reshare - old Diaspora version
547                         return self::fetch_message($source_xml->post->reshare->root_guid, $server, ++$level);
548                 } elseif ($source_xml->getName() == "reshare") {
549                         // Reshare of a reshare - new Diaspora version
550                         return self::fetch_message($source_xml->root_guid, $server, ++$level);
551                 }
552
553                 $author = "";
554
555                 // Fetch the author - for the old and the new Diaspora version
556                 if ($source_xml->post->status_message->diaspora_handle)
557                         $author = (string)$source_xml->post->status_message->diaspora_handle;
558                 elseif ($source_xml->author AND ($source_xml->getName() == "status_message"))
559                         $author = (string)$source_xml->author;
560
561                 // If this isn't a "status_message" then quit
562                 if (!$author)
563                         return false;
564
565                 $msg = array("message" => $x, "author" => $author);
566
567                 $msg["key"] = self::get_key($msg["author"]);
568
569                 return $msg;
570         }
571
572         private function fetch_parent_item($uid, $guid, $author, $contact) {
573                 $r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `origin`,
574                                 `author-name`, `author-link`, `author-avatar`,
575                                 `owner-name`, `owner-link`, `owner-avatar`
576                         FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
577                         intval($uid), dbesc($guid));
578
579                 if(!$r) {
580                         $result = self::store_by_guid($guid, $contact["url"], $uid);
581
582                         if (!$result) {
583                                 $person = self::get_person_by_handle($author);
584                                 $result = self::store_by_guid($guid, $person["url"], $uid);
585                         }
586
587                         if ($result) {
588                                 logger("Fetched missing item ".$guid." - result: ".$result, LOGGER_DEBUG);
589
590                                 $r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `origin`,
591                                                 `author-name`, `author-link`, `author-avatar`,
592                                                 `owner-name`, `owner-link`, `owner-avatar`
593                                         FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
594                                         intval($uid), dbesc($guid));
595                         }
596                 }
597
598                 if (!$r) {
599                         logger("parent item not found: parent: ".$guid." item: ".$guid);
600                         return false;
601                 } else
602                         return $r[0];
603         }
604
605         private function get_author_contact_by_url($contact, $person, $uid) {
606
607                 $r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1",
608                         dbesc(normalise_link($person["url"])), intval($uid));
609                 if ($r) {
610                         $cid = $r[0]["id"];
611                         $network = $r[0]["network"];
612                 } else {
613                         $cid = $contact["id"];
614                         $network = NETWORK_DIASPORA;
615                 }
616
617                 return (array("cid" => $cid, "network" => $network));
618         }
619
620         public static function is_redmatrix($url) {
621                 return(strstr($url, "/channel/"));
622         }
623
624         private function plink($addr, $guid) {
625                 $r = q("SELECT `url`, `nick`, `network` FROM `fcontact` WHERE `addr`='%s' LIMIT 1", dbesc($addr));
626
627                 // Fallback
628                 if (!$r)
629                         return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid;
630
631                 // Friendica contacts are often detected as Diaspora contacts in the "fcontact" table
632                 // So we try another way as well.
633                 $s = q("SELECT `network` FROM `gcontact` WHERE `nurl`='%s' LIMIT 1", dbesc(normalise_link($r[0]["url"])));
634                 if ($s)
635                         $r[0]["network"] = $s[0]["network"];
636
637                 if ($r[0]["network"] == NETWORK_DFRN)
638                         return(str_replace("/profile/".$r[0]["nick"]."/", "/display/".$guid, $r[0]["url"]."/"));
639
640                 if (self::is_redmatrix($r[0]["url"]))
641                         return $r[0]["url"]."/?f=&mid=".$guid;
642
643                 return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid;
644         }
645
646         private function receive_account_deletion($importer, $data) {
647                 $author = notags(unxmlify($data->author));
648
649                 $contact = self::get_contact_by_handle($importer["uid"], $author);
650                 if (!$contact) {
651                         logger("cannot find contact for author: ".$author);
652                         return false;
653                 }
654
655                 // We now remove the contact
656                 contact_remove($contact["id"]);
657                 return true;
658         }
659
660         private function receive_comment($importer, $sender, $data) {
661                 $guid = notags(unxmlify($data->guid));
662                 $parent_guid = notags(unxmlify($data->parent_guid));
663                 $text = unxmlify($data->text);
664                 $author = notags(unxmlify($data->author));
665
666                 $contact = self::get_allowed_contact_by_handle($importer, $sender, true);
667                 if (!$contact)
668                         return false;
669
670                 if (self::message_exists($importer["uid"], $guid))
671                         return false;
672
673                 $parent_item = self::fetch_parent_item($importer["uid"], $parent_guid, $author, $contact);
674                 if (!$parent_item)
675                         return false;
676
677                 $person = self::get_person_by_handle($author);
678                 if (!is_array($person)) {
679                         logger("unable to find author details");
680                         return false;
681                 }
682
683                 // Fetch the contact id - if we know this contact
684                 $author_contact = self::get_author_contact_by_url($contact, $person, $importer["uid"]);
685
686                 $datarray = array();
687
688                 $datarray["uid"] = $importer["uid"];
689                 $datarray["contact-id"] = $author_contact["cid"];
690                 $datarray["network"]  = $author_contact["network"];
691
692                 $datarray["author-name"] = $person["name"];
693                 $datarray["author-link"] = $person["url"];
694                 $datarray["author-avatar"] = ((x($person,"thumb")) ? $person["thumb"] : $person["photo"]);
695
696                 $datarray["owner-name"] = $contact["name"];
697                 $datarray["owner-link"] = $contact["url"];
698                 $datarray["owner-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]);
699
700                 $datarray["guid"] = $guid;
701                 $datarray["uri"] = $author.":".$guid;
702
703                 $datarray["type"] = "remote-comment";
704                 $datarray["verb"] = ACTIVITY_POST;
705                 $datarray["gravity"] = GRAVITY_COMMENT;
706                 $datarray["parent-uri"] = $parent_item["uri"];
707
708                 $datarray["object-type"] = ACTIVITY_OBJ_COMMENT;
709                 $datarray["object"] = json_encode($data);
710
711                 $datarray["body"] = diaspora2bb($text);
712
713                 self::fetch_guid($datarray);
714
715                 $message_id = item_store($datarray);
716                 // print_r($datarray);
717
718                 // If we are the origin of the parent we store the original data and notify our followers
719                 if($message_id AND $parent_item["origin"]) {
720
721                         // Formerly we stored the signed text, the signature and the author in different fields.
722                         // We now store the raw data so that we are more flexible.
723                         q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')",
724                                 intval($message_id),
725                                 dbesc(json_encode($data))
726                         );
727
728                         // notify others
729                         proc_run("php", "include/notifier.php", "comment-import", $message_id);
730                 }
731
732                 return $message_id;
733         }
734
735         private function receive_conversation_message($importer, $contact, $data, $msg, $mesg) {
736                 $guid = notags(unxmlify($data->guid));
737                 $subject = notags(unxmlify($data->subject));
738                 $author = notags(unxmlify($data->author));
739
740                 $reply = 0;
741
742                 $msg_guid = notags(unxmlify($mesg->guid));
743                 $msg_parent_guid = notags(unxmlify($mesg->parent_guid));
744                 $msg_parent_author_signature = notags(unxmlify($mesg->parent_author_signature));
745                 $msg_author_signature = notags(unxmlify($mesg->author_signature));
746                 $msg_text = unxmlify($mesg->text);
747                 $msg_created_at = datetime_convert("UTC", "UTC", notags(unxmlify($mesg->created_at)));
748
749                 // "diaspora_handle" is the element name from the old version
750                 // "author" is the element name from the new version
751                 if ($mesg->author)
752                         $msg_author = notags(unxmlify($mesg->author));
753                 elseif ($mesg->diaspora_handle)
754                         $msg_author = notags(unxmlify($mesg->diaspora_handle));
755                 else
756                         return false;
757
758                 $msg_conversation_guid = notags(unxmlify($mesg->conversation_guid));
759
760                 if($msg_conversation_guid != $guid) {
761                         logger("message conversation guid does not belong to the current conversation.");
762                         return false;
763                 }
764
765                 $body = diaspora2bb($msg_text);
766                 $message_uri = $msg_author.":".$msg_guid;
767
768                 $author_signed_data = $msg_guid.";".$msg_parent_guid.";".$msg_text.";".unxmlify($mesg->created_at).";".$msg_author.";".$msg_conversation_guid;
769
770                 $author_signature = base64_decode($msg_author_signature);
771
772                 if(strcasecmp($msg_author,$msg["author"]) == 0) {
773                         $person = $contact;
774                         $key = $msg["key"];
775                 } else {
776                         $person = self::get_person_by_handle($msg_author);
777
778                         if (is_array($person) && x($person, "pubkey"))
779                                 $key = $person["pubkey"];
780                         else {
781                                 logger("unable to find author details");
782                                         return false;
783                         }
784                 }
785
786                 if (!rsa_verify($author_signed_data, $author_signature, $key, "sha256")) {
787                         logger("verification failed.");
788                         return false;
789                 }
790
791                 if($msg_parent_author_signature) {
792                         $owner_signed_data = $msg_guid.";".$msg_parent_guid.";".$msg_text.";".unxmlify($mesg->created_at).";".$msg_author.";".$msg_conversation_guid;
793
794                         $parent_author_signature = base64_decode($msg_parent_author_signature);
795
796                         $key = $msg["key"];
797
798                         if (!rsa_verify($owner_signed_data, $parent_author_signature, $key, "sha256")) {
799                                 logger("owner verification failed.");
800                                 return false;
801                         }
802                 }
803
804                 $r = q("SELECT `id` FROM `mail` WHERE `uri` = '%s' LIMIT 1",
805                         dbesc($message_uri)
806                 );
807                 if($r) {
808                         logger("duplicate message already delivered.", LOGGER_DEBUG);
809                         return false;
810                 }
811
812                 q("INSERT INTO `mail` (`uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`)
813                         VALUES (%d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')",
814                         intval($importer["uid"]),
815                         dbesc($msg_guid),
816                         intval($conversation["id"]),
817                         dbesc($person["name"]),
818                         dbesc($person["photo"]),
819                         dbesc($person["url"]),
820                         intval($contact["id"]),
821                         dbesc($subject),
822                         dbesc($body),
823                         0,
824                         0,
825                         dbesc($message_uri),
826                         dbesc($author.":".$guid),
827                         dbesc($msg_created_at)
828                 );
829
830                 q("UPDATE `conv` SET `updated` = '%s' WHERE `id` = %d",
831                         dbesc(datetime_convert()),
832                         intval($conversation["id"])
833                 );
834
835                 notification(array(
836                         "type" => NOTIFY_MAIL,
837                         "notify_flags" => $importer["notify-flags"],
838                         "language" => $importer["language"],
839                         "to_name" => $importer["username"],
840                         "to_email" => $importer["email"],
841                         "uid" =>$importer["uid"],
842                         "item" => array("subject" => $subject, "body" => $body),
843                         "source_name" => $person["name"],
844                         "source_link" => $person["url"],
845                         "source_photo" => $person["thumb"],
846                         "verb" => ACTIVITY_POST,
847                         "otype" => "mail"
848                 ));
849         }
850
851         private function receive_conversation($importer, $msg, $data) {
852                 $guid = notags(unxmlify($data->guid));
853                 $subject = notags(unxmlify($data->subject));
854                 $created_at = datetime_convert("UTC", "UTC", notags(unxmlify($data->created_at)));
855                 $author = notags(unxmlify($data->author));
856                 $participants = notags(unxmlify($data->participants));
857
858                 $messages = $data->message;
859
860                 if (!count($messages)) {
861                         logger("empty conversation");
862                         return false;
863                 }
864
865                 $contact = self::get_allowed_contact_by_handle($importer, $msg["author"], true);
866                 if (!$contact)
867                         return false;
868
869                 $conversation = null;
870
871                 $c = q("SELECT * FROM `conv` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
872                         intval($importer["uid"]),
873                         dbesc($guid)
874                 );
875                 if($c)
876                         $conversation = $c[0];
877                 else {
878                         $r = q("INSERT INTO `conv` (`uid`, `guid`, `creator`, `created`, `updated`, `subject`, `recips`)
879                                 VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s')",
880                                 intval($importer["uid"]),
881                                 dbesc($guid),
882                                 dbesc($author),
883                                 dbesc(datetime_convert("UTC", "UTC", $created_at)),
884                                 dbesc(datetime_convert()),
885                                 dbesc($subject),
886                                 dbesc($participants)
887                         );
888                         if($r)
889                                 $c = q("SELECT * FROM `conv` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
890                                         intval($importer["uid"]),
891                                         dbesc($guid)
892                                 );
893
894                         if($c)
895                                 $conversation = $c[0];
896                 }
897                 if (!$conversation) {
898                         logger("unable to create conversation.");
899                         return;
900                 }
901
902                 foreach($messages as $mesg)
903                         self::receive_conversation_message($importer, $contact, $data, $msg, $mesg);
904
905                 return true;
906         }
907
908         private function construct_like_body($contact, $parent_item, $guid) {
909                 $bodyverb = t('%1$s likes %2$s\'s %3$s');
910
911                 $ulink = "[url=".$contact["url"]."]".$contact["name"]."[/url]";
912                 $alink = "[url=".$parent_item["author-link"]."]".$parent_item["author-name"]."[/url]";
913                 $plink = "[url=".App::get_baseurl()."/display/".urlencode($guid)."]".t("status")."[/url]";
914
915                 return sprintf($bodyverb, $ulink, $alink, $plink);
916         }
917
918         private function construct_like_object($importer, $parent_item) {
919                 $objtype = ACTIVITY_OBJ_NOTE;
920                 $link = xmlify('<link rel="alternate" type="text/html" href="'.App::get_baseurl()."/display/".$importer["nickname"]."/".$parent_item["id"].'" />'."\n") ;
921                 $parent_body = $parent_item["body"];
922
923                 $obj = <<< EOT
924
925                 <object>
926                         <type>$objtype</type>
927                         <local>1</local>
928                         <id>{$parent_item["uri"]}</id>
929                         <link>$link</link>
930                         <title></title>
931                         <content>$parent_body</content>
932                 </object>
933 EOT;
934
935                 return $obj;
936         }
937
938         private function receive_like($importer, $sender, $data) {
939                 $positive = notags(unxmlify($data->positive));
940                 $guid = notags(unxmlify($data->guid));
941                 $parent_type = notags(unxmlify($data->parent_type));
942                 $parent_guid = notags(unxmlify($data->parent_guid));
943                 $author = notags(unxmlify($data->author));
944
945                 // likes on comments aren't supported by Diaspora - only on posts
946                 // But maybe this will be supported in the future, so we will accept it.
947                 if (!in_array($parent_type, array("Post", "Comment")))
948                         return false;
949
950                 $contact = self::get_allowed_contact_by_handle($importer, $sender, true);
951                 if (!$contact)
952                         return false;
953
954                 if (self::message_exists($importer["uid"], $guid))
955                         return false;
956
957                 $parent_item = self::fetch_parent_item($importer["uid"], $parent_guid, $author, $contact);
958                 if (!$parent_item)
959                         return false;
960
961                 $person = self::get_person_by_handle($author);
962                 if (!is_array($person)) {
963                         logger("unable to find author details");
964                         return false;
965                 }
966
967                 // Fetch the contact id - if we know this contact
968                 $author_contact = self::get_author_contact_by_url($contact, $person, $importer["uid"]);
969
970                 // "positive" = "false" would be a Dislike - wich isn't currently supported by Diaspora
971                 // We would accept this anyhow.
972                 if ($positive === "true")
973                         $verb = ACTIVITY_LIKE;
974                 else
975                         $verb = ACTIVITY_DISLIKE;
976
977                 $datarray = array();
978
979                 $datarray["uid"] = $importer["uid"];
980                 $datarray["contact-id"] = $author_contact["cid"];
981                 $datarray["network"]  = $author_contact["network"];
982
983                 $datarray["author-name"] = $person["name"];
984                 $datarray["author-link"] = $person["url"];
985                 $datarray["author-avatar"] = ((x($person,"thumb")) ? $person["thumb"] : $person["photo"]);
986
987                 $datarray["owner-name"] = $contact["name"];
988                 $datarray["owner-link"] = $contact["url"];
989                 $datarray["owner-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]);
990
991                 $datarray["guid"] = $guid;
992                 $datarray["uri"] = $author.":".$guid;
993
994                 $datarray["type"] = "activity";
995                 $datarray["verb"] = $verb;
996                 $datarray["gravity"] = GRAVITY_LIKE;
997                 $datarray["parent-uri"] = $parent_item["uri"];
998
999                 $datarray["object-type"] = ACTIVITY_OBJ_NOTE;
1000                 $datarray["object"] = self::construct_like_object($importer, $parent_item);
1001
1002                 $datarray["body"] = self::construct_like_body($contact, $parent_item, $guid);
1003
1004                 $message_id = item_store($datarray);
1005                 // print_r($datarray);
1006
1007                 // If we are the origin of the parent we store the original data and notify our followers
1008                 if($message_id AND $parent_item["origin"]) {
1009
1010                         // Formerly we stored the signed text, the signature and the author in different fields.
1011                         // We now store the raw data so that we are more flexible.
1012                         q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')",
1013                                 intval($message_id),
1014                                 dbesc(json_encode($data))
1015                         );
1016
1017                         // notify others
1018                         proc_run("php", "include/notifier.php", "comment-import", $message_id);
1019                 }
1020
1021                 return $message_id;
1022         }
1023
1024         private function receive_message($importer, $data) {
1025                 $guid = notags(unxmlify($data->guid));
1026                 $parent_guid = notags(unxmlify($data->parent_guid));
1027                 $text = unxmlify($data->text);
1028                 $created_at = datetime_convert("UTC", "UTC", notags(unxmlify($data->created_at)));
1029                 $author = notags(unxmlify($data->author));
1030                 $conversation_guid = notags(unxmlify($data->conversation_guid));
1031
1032                 $contact = self::get_allowed_contact_by_handle($importer, $author, true);
1033                 if (!$contact)
1034                         return false;
1035
1036                 $conversation = null;
1037
1038                 $c = q("SELECT * FROM `conv` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
1039                         intval($importer["uid"]),
1040                         dbesc($conversation_guid)
1041                 );
1042                 if($c)
1043                         $conversation = $c[0];
1044                 else {
1045                         logger("conversation not available.");
1046                         return false;
1047                 }
1048
1049                 $reply = 0;
1050
1051                 $body = diaspora2bb($text);
1052                 $message_uri = $author.":".$guid;
1053
1054                 $person = self::get_person_by_handle($author);
1055                 if (!$person) {
1056                         logger("unable to find author details");
1057                         return false;
1058                 }
1059
1060                 $r = q("SELECT `id` FROM `mail` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1061                         dbesc($message_uri),
1062                         intval($importer["uid"])
1063                 );
1064                 if($r) {
1065                         logger("duplicate message already delivered.", LOGGER_DEBUG);
1066                         return false;
1067                 }
1068
1069                 q("INSERT INTO `mail` (`uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`)
1070                                 VALUES ( %d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')",
1071                         intval($importer["uid"]),
1072                         dbesc($guid),
1073                         intval($conversation["id"]),
1074                         dbesc($person["name"]),
1075                         dbesc($person["photo"]),
1076                         dbesc($person["url"]),
1077                         intval($contact["id"]),
1078                         dbesc($conversation["subject"]),
1079                         dbesc($body),
1080                         0,
1081                         1,
1082                         dbesc($message_uri),
1083                         dbesc($author.":".$parent_guid),
1084                         dbesc($created_at)
1085                 );
1086
1087                 q("UPDATE `conv` SET `updated` = '%s' WHERE `id` = %d",
1088                         dbesc(datetime_convert()),
1089                         intval($conversation["id"])
1090                 );
1091
1092                 return true;
1093         }
1094
1095         private function receive_participation($importer, $data) {
1096                 // I'm not sure if we can fully support this message type
1097                 return true;
1098         }
1099
1100         private function receive_photo($importer, $data) {
1101                 // There doesn't seem to be a reason for this function, since the photo data is transmitted in the status message as well
1102                 return true;
1103         }
1104
1105         private function receive_poll_participation($importer, $data) {
1106                 // We don't support polls by now
1107                 return true;
1108         }
1109
1110         private function receive_profile($importer, $data) {
1111                 $author = notags(unxmlify($data->author));
1112
1113                 $contact = self::get_contact_by_handle($importer["uid"], $author);
1114                 if (!$contact)
1115                         return;
1116
1117                 $name = unxmlify($data->first_name).((strlen($data->last_name)) ? " ".unxmlify($data->last_name) : "");
1118                 $image_url = unxmlify($data->image_url);
1119                 $birthday = unxmlify($data->birthday);
1120                 $location = diaspora2bb(unxmlify($data->location));
1121                 $about = diaspora2bb(unxmlify($data->bio));
1122                 $gender = unxmlify($data->gender);
1123                 $searchable = (unxmlify($data->searchable) == "true");
1124                 $nsfw = (unxmlify($data->nsfw) == "true");
1125                 $tags = unxmlify($data->tag_string);
1126
1127                 $tags = explode("#", $tags);
1128
1129                 $keywords = array();
1130                 foreach ($tags as $tag) {
1131                         $tag = trim(strtolower($tag));
1132                         if ($tag != "")
1133                                 $keywords[] = $tag;
1134                 }
1135
1136                 $keywords = implode(", ", $keywords);
1137
1138                 $handle_parts = explode("@", $author);
1139                 $nick = $handle_parts[0];
1140
1141                 if($name === "")
1142                         $name = $handle_parts[0];
1143
1144                 if( preg_match("|^https?://|", $image_url) === 0)
1145                         $image_url = "http://".$handle_parts[1].$image_url;
1146
1147                 update_contact_avatar($image_url, $importer["uid"], $contact["id"]);
1148
1149                 // Generic birthday. We don't know the timezone. The year is irrelevant.
1150
1151                 $birthday = str_replace("1000", "1901", $birthday);
1152
1153                 if ($birthday != "")
1154                         $birthday = datetime_convert("UTC", "UTC", $birthday, "Y-m-d");
1155
1156                 // this is to prevent multiple birthday notifications in a single year
1157                 // if we already have a stored birthday and the 'm-d' part hasn't changed, preserve the entry, which will preserve the notify year
1158
1159                 if(substr($birthday,5) === substr($contact["bd"],5))
1160                         $birthday = $contact["bd"];
1161
1162                 $r = q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `addr` = '%s', `name-date` = '%s', `bd` = '%s',
1163                                 `location` = '%s', `about` = '%s', `keywords` = '%s', `gender` = '%s' WHERE `id` = %d AND `uid` = %d",
1164                         dbesc($name),
1165                         dbesc($nick),
1166                         dbesc($author),
1167                         dbesc(datetime_convert()),
1168                         dbesc($birthday),
1169                         dbesc($location),
1170                         dbesc($about),
1171                         dbesc($keywords),
1172                         dbesc($gender),
1173                         intval($contact["id"]),
1174                         intval($importer["uid"])
1175                 );
1176
1177                 if ($searchable) {
1178                         poco_check($contact["url"], $name, NETWORK_DIASPORA, $image_url, $about, $location, $gender, $keywords, "",
1179                                 datetime_convert(), 2, $contact["id"], $importer["uid"]);
1180                 }
1181
1182                 $gcontact = array("url" => $contact["url"], "network" => NETWORK_DIASPORA, "generation" => 2,
1183                                         "photo" => $image_url, "name" => $name, "location" => $location,
1184                                         "about" => $about, "birthday" => $birthday, "gender" => $gender,
1185                                         "addr" => $author, "nick" => $nick, "keywords" => $keywords,
1186                                         "hide" => !$searchable, "nsfw" => $nsfw);
1187
1188                 update_gcontact($gcontact);
1189
1190                 return true;
1191         }
1192
1193         private function receive_request_make_friend($importer, $contact) {
1194
1195                 $a = get_app();
1196
1197                 if($contact["rel"] == CONTACT_IS_FOLLOWER && in_array($importer["page-flags"], array(PAGE_FREELOVE))) {
1198                         q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
1199                                 intval(CONTACT_IS_FRIEND),
1200                                 intval($contact["id"]),
1201                                 intval($importer["uid"])
1202                         );
1203                 }
1204                 // send notification
1205
1206                 $r = q("SELECT `hide-friends` FROM `profile` WHERE `uid` = %d AND `is-default` = 1 LIMIT 1",
1207                         intval($importer["uid"])
1208                 );
1209
1210                 if($r && !$r[0]["hide-friends"] && !$contact["hidden"] && intval(get_pconfig($importer["uid"], "system", "post_newfriend"))) {
1211
1212                         $self = q("SELECT * FROM `contact` WHERE `self` AND `uid` = %d LIMIT 1",
1213                                 intval($importer["uid"])
1214                         );
1215
1216                         // they are not CONTACT_IS_FOLLOWER anymore but that's what we have in the array
1217
1218                         if($self && $contact["rel"] == CONTACT_IS_FOLLOWER) {
1219
1220                                 $arr = array();
1221                                 $arr["uri"] = $arr["parent-uri"] = item_new_uri($a->get_hostname(), $importer["uid"]);
1222                                 $arr["uid"] = $importer["uid"];
1223                                 $arr["contact-id"] = $self[0]["id"];
1224                                 $arr["wall"] = 1;
1225                                 $arr["type"] = 'wall';
1226                                 $arr["gravity"] = 0;
1227                                 $arr["origin"] = 1;
1228                                 $arr["author-name"] = $arr["owner-name"] = $self[0]["name"];
1229                                 $arr["author-link"] = $arr["owner-link"] = $self[0]["url"];
1230                                 $arr["author-avatar"] = $arr["owner-avatar"] = $self[0]["thumb"];
1231                                 $arr["verb"] = ACTIVITY_FRIEND;
1232                                 $arr["object-type"] = ACTIVITY_OBJ_PERSON;
1233
1234                                 $A = "[url=".$self[0]["url"]."]".$self[0]["name"]."[/url]";
1235                                 $B = "[url=".$contact["url"]."]".$contact["name"]."[/url]";
1236                                 $BPhoto = "[url=".$contact["url"]."][img]".$contact["thumb"]."[/img][/url]";
1237                                 $arr["body"] = sprintf(t("%1$s is now friends with %2$s"), $A, $B)."\n\n\n".$Bphoto;
1238
1239                                 $arr["object"] = "<object><type>".ACTIVITY_OBJ_PERSON."</type><title>".$contact["name"]."</title>"
1240                                         ."<id>".$contact["url"]."/".$contact["name"]."</id>";
1241                                 $arr["object"] .= "<link>".xmlify('<link rel="alternate" type="text/html" href="'.$contact["url"].'" />'."\n");
1242                                 $arr["object"] .= xmlify('<link rel="photo" type="image/jpeg" href="'.$contact["thumb"].'" />'."\n");
1243                                 $arr["object"] .= "</link></object>\n";
1244                                 $arr["last-child"] = 1;
1245
1246                                 $arr["allow_cid"] = $user[0]["allow_cid"];
1247                                 $arr["allow_gid"] = $user[0]["allow_gid"];
1248                                 $arr["deny_cid"]  = $user[0]["deny_cid"];
1249                                 $arr["deny_gid"]  = $user[0]["deny_gid"];
1250
1251                                 $i = item_store($arr);
1252                                 if($i)
1253                                         proc_run("php", "include/notifier.php", "activity", $i);
1254
1255                         }
1256
1257                 }
1258         }
1259
1260         private function receive_request($importer, $data) {
1261                 $author = unxmlify($data->author);
1262                 $recipient = unxmlify($data->recipient);
1263
1264                 if (!$author || !$recipient)
1265                         return;
1266
1267                 $contact = self::get_contact_by_handle($importer["uid"],$author);
1268
1269                 if($contact) {
1270
1271                         // perhaps we were already sharing with this person. Now they're sharing with us.
1272                         // That makes us friends.
1273
1274                         self::receive_request_make_friend($importer, $contact);
1275                         return true;
1276                 }
1277
1278                 $ret = self::get_person_by_handle($author);
1279
1280                 if (!$ret || ($ret["network"] != NETWORK_DIASPORA)) {
1281                         logger("Cannot resolve diaspora handle ".$author ." for ".$recipient);
1282                         return false;
1283                 }
1284
1285                 $batch = (($ret["batch"]) ? $ret["batch"] : implode("/", array_slice(explode("/", $ret["url"]), 0, 3))."/receive/public");
1286
1287                 $r = q("INSERT INTO `contact` (`uid`, `network`,`addr`,`created`,`url`,`nurl`,`batch`,`name`,`nick`,`photo`,`pubkey`,`notify`,`poll`,`blocked`,`priority`)
1288                         VALUES (%d, '%s', '%s', '%s', '%s','%s','%s','%s','%s','%s','%s','%s','%s',%d,%d)",
1289                         intval($importer["uid"]),
1290                         dbesc($ret["network"]),
1291                         dbesc($ret["addr"]),
1292                         datetime_convert(),
1293                         dbesc($ret["url"]),
1294                         dbesc(normalise_link($ret["url"])),
1295                         dbesc($batch),
1296                         dbesc($ret["name"]),
1297                         dbesc($ret["nick"]),
1298                         dbesc($ret["photo"]),
1299                         dbesc($ret["pubkey"]),
1300                         dbesc($ret["notify"]),
1301                         dbesc($ret["poll"]),
1302                         1,
1303                         2
1304                 );
1305
1306                 // find the contact record we just created
1307
1308                 $contact_record = self::get_contact_by_handle($importer["uid"],$author);
1309
1310                 if (!$contact_record) {
1311                         logger("unable to locate newly created contact record.");
1312                         return;
1313                 }
1314
1315                 $g = q("SELECT `def_gid` FROM `user` WHERE `uid` = %d LIMIT 1",
1316                         intval($importer["uid"])
1317                 );
1318
1319                 if($g && intval($g[0]["def_gid"]))
1320                         group_add_member($importer["uid"], "", $contact_record["id"], $g[0]["def_gid"]);
1321
1322                 if($importer["page-flags"] == PAGE_NORMAL) {
1323
1324                         $hash = random_string().(string)time();   // Generate a confirm_key
1325
1326                         $ret = q("INSERT INTO `intro` (`uid`, `contact-id`, `blocked`, `knowyou`, `note`, `hash`, `datetime`)
1327                                 VALUES (%d, %d, %d, %d, '%s', '%s', '%s')",
1328                                 intval($importer["uid"]),
1329                                 intval($contact_record["id"]),
1330                                 0,
1331                                 0,
1332                                 dbesc(t("Sharing notification from Diaspora network")),
1333                                 dbesc($hash),
1334                                 dbesc(datetime_convert())
1335                         );
1336                 } else {
1337
1338                         // automatic friend approval
1339
1340                         update_contact_avatar($contact_record["photo"],$importer["uid"],$contact_record["id"]);
1341
1342                         // technically they are sharing with us (CONTACT_IS_SHARING),
1343                         // but if our page-type is PAGE_COMMUNITY or PAGE_SOAPBOX
1344                         // we are going to change the relationship and make them a follower.
1345
1346                         if($importer["page-flags"] == PAGE_FREELOVE)
1347                                 $new_relation = CONTACT_IS_FRIEND;
1348                         else
1349                                 $new_relation = CONTACT_IS_FOLLOWER;
1350
1351                         $r = q("UPDATE `contact` SET `rel` = %d,
1352                                 `name-date` = '%s',
1353                                 `uri-date` = '%s',
1354                                 `blocked` = 0,
1355                                 `pending` = 0,
1356                                 `writable` = 1
1357                                 WHERE `id` = %d
1358                                 ",
1359                                 intval($new_relation),
1360                                 dbesc(datetime_convert()),
1361                                 dbesc(datetime_convert()),
1362                                 intval($contact_record["id"])
1363                         );
1364
1365                         $u = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval($importer["uid"]));
1366                         if($u)
1367                                 $ret = diaspora_share($u[0], $contact_record);
1368                 }
1369
1370                 return true;
1371         }
1372
1373         private function get_original_item($guid, $orig_author, $author) {
1374
1375                 // Do we already have this item?
1376                 $r = q("SELECT `body`, `tag`, `app`, `created`, `object-type`, `uri`, `guid`,
1377                                 `author-name`, `author-link`, `author-avatar`
1378                                 FROM `item` WHERE `guid` = '%s' AND `visible` AND NOT `deleted` AND `body` != '' LIMIT 1",
1379                         dbesc($guid));
1380
1381                 if($r) {
1382                         logger("reshared message ".$guid." already exists on system.");
1383
1384                         // Maybe it is already a reshared item?
1385                         // Then refetch the content, since there can be many side effects with reshared posts from other networks or reshares from reshares
1386                         if (self::is_reshare($r[0]["body"]))
1387                                 $r = array();
1388                         else
1389                                 return $r[0];
1390                 }
1391
1392                 if (!$r) {
1393                         $server = "https://".substr($orig_author, strpos($orig_author, "@") + 1);
1394                         logger("1st try: reshared message ".$guid." will be fetched from original server: ".$server);
1395                         $item_id = self::store_by_guid($guid, $server);
1396
1397                         if (!$item_id) {
1398                                 $server = "http://".substr($orig_author, strpos($orig_author, "@") + 1);
1399                                 logger("2nd try: reshared message ".$guid." will be fetched from original server: ".$server);
1400                                 $item_id = self::store_by_guid($guid, $server);
1401                         }
1402
1403                         // Deactivated by now since there is a risk that someone could manipulate postings through this method
1404 /*                      if (!$item_id) {
1405                                 $server = "https://".substr($author, strpos($author, "@") + 1);
1406                                 logger("3rd try: reshared message ".$guid." will be fetched from sharer's server: ".$server);
1407                                 $item_id = self::store_by_guid($guid, $server);
1408                         }
1409                         if (!$item_id) {
1410                                 $server = "http://".substr($author, strpos($author, "@") + 1);
1411                                 logger("4th try: reshared message ".$guid." will be fetched from sharer's server: ".$server);
1412                                 $item_id = self::store_by_guid($guid, $server);
1413                         }
1414 */
1415                         if ($item_id) {
1416                                 $r = q("SELECT `body`, `tag`, `app`, `created`, `object-type`, `uri`, `guid`,
1417                                                 `author-name`, `author-link`, `author-avatar`
1418                                         FROM `item` WHERE `id` = %d AND `visible` AND NOT `deleted` AND `body` != '' LIMIT 1",
1419                                         intval($item_id));
1420
1421                                 if ($r)
1422                                         return $r[0];
1423
1424                         }
1425                 }
1426                 return false;
1427         }
1428
1429         private function receive_reshare($importer, $data) {
1430                 $root_author = notags(unxmlify($data->root_author));
1431                 $root_guid = notags(unxmlify($data->root_guid));
1432                 $guid = notags(unxmlify($data->guid));
1433                 $author = notags(unxmlify($data->author));
1434                 $public = notags(unxmlify($data->public));
1435                 $created_at = notags(unxmlify($data->created_at));
1436
1437                 $contact = self::get_allowed_contact_by_handle($importer, $author, false);
1438                 if (!$contact)
1439                         return false;
1440
1441                 if (self::message_exists($importer["uid"], $guid))
1442                         return false;
1443
1444                 $original_item = self::get_original_item($root_guid, $root_author, $author);
1445                 if (!$original_item)
1446                         return false;
1447
1448                 $datarray = array();
1449
1450                 $datarray["uid"] = $importer["uid"];
1451                 $datarray["contact-id"] = $contact["id"];
1452                 $datarray["network"]  = NETWORK_DIASPORA;
1453
1454                 $datarray["author-name"] = $contact["name"];
1455                 $datarray["author-link"] = $contact["url"];
1456                 $datarray["author-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]);
1457
1458                 $datarray["owner-name"] = $datarray["author-name"];
1459                 $datarray["owner-link"] = $datarray["author-link"];
1460                 $datarray["owner-avatar"] = $datarray["author-avatar"];
1461
1462                 $datarray["guid"] = $guid;
1463                 $datarray["uri"] = $datarray["parent-uri"] = $author.":".$guid;
1464
1465                 $datarray["verb"] = ACTIVITY_POST;
1466                 $datarray["gravity"] = GRAVITY_PARENT;
1467
1468                 $datarray["object"] = json_encode($data);
1469
1470                 $prefix = share_header($original_item["author-name"], $original_item["author-link"], $original_item["author-avatar"],
1471                                         $original_item["guid"], $original_item["created"], $original_item["uri"]);
1472                 $datarray["body"] = $prefix.$original_item["body"]."[/share]";
1473
1474                 $datarray["tag"] = $original_item["tag"];
1475                 $datarray["app"]  = $original_item["app"];
1476
1477                 $datarray["plink"] = self::plink($author, $guid);
1478                 $datarray["private"] = (($public == "false") ? 1 : 0);
1479                 $datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert("UTC", "UTC", $created_at);
1480
1481                 $datarray["object-type"] = $original_item["object-type"];
1482
1483                 self::fetch_guid($datarray);
1484                 $message_id = item_store($datarray);
1485                 // print_r($datarray);
1486
1487                 return $message_id;
1488         }
1489
1490         private function item_retraction($importer, $contact, $data) {
1491                 $target_type = notags(unxmlify($data->target_type));
1492                 $target_guid = notags(unxmlify($data->target_guid));
1493                 $author = notags(unxmlify($data->author));
1494
1495                 $person = self::get_person_by_handle($author);
1496                 if (!is_array($person)) {
1497                         logger("unable to find author detail for ".$author);
1498                         return false;
1499                 }
1500
1501                 $r = q("SELECT `id`, `parent`, `parent-uri`, `author-link` FROM `item` WHERE `guid` = '%s' AND `uid` = %d AND NOT `file` LIKE '%%[%%' LIMIT 1",
1502                         dbesc($target_guid),
1503                         intval($importer["uid"])
1504                 );
1505                 if (!$r)
1506                         return false;
1507
1508                 // Only delete it if the author really fits
1509                 if (!link_compare($r[0]["author-link"],$person["url"]))
1510                         return false;
1511
1512                 // Check if the sender is the thread owner
1513                 $p = q("SELECT `author-link`, `origin` FROM `item` WHERE `id` = %d",
1514                         intval($r[0]["parent"]));
1515
1516                 // Only delete it if the parent author really fits
1517                 if (!link_compare($p[0]["author-link"], $contact["url"]))
1518                         return false;
1519
1520                 // Currently we don't have a central deletion function that we could use in this case. The function "item_drop" doesn't work for that case
1521                 q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = '' WHERE `id` = %d",
1522                         dbesc(datetime_convert()),
1523                         dbesc(datetime_convert()),
1524                         intval($r[0]["id"])
1525                 );
1526                 delete_thread($r[0]["id"], $r[0]["parent-uri"]);
1527
1528                 // Now check if the retraction needs to be relayed by us
1529                 if($p[0]["origin"]) {
1530
1531                         // Formerly we stored the signed text, the signature and the author in different fields.
1532                         // We now store the raw data so that we are more flexible.
1533                         q("INSERT INTO `sign` (`retract_iid`,`signed_text`) VALUES (%d,'%s')",
1534                                 intval($r[0]["id"]),
1535                                 dbesc(json_encode($data))
1536                         );
1537
1538                         // notify others
1539                         proc_run("php", "include/notifier.php", "drop", $r[0]["id"]);
1540                 }
1541         }
1542
1543         private function receive_retraction($importer, $sender, $data) {
1544                 $target_type = notags(unxmlify($data->target_type));
1545
1546                 $contact = self::get_contact_by_handle($importer["uid"], $sender);
1547                 if (!$contact) {
1548                         logger("cannot find contact for sender: ".$sender." and user ".$importer["uid"]);
1549                         return false;
1550                 }
1551
1552                 switch ($target_type) {
1553                         case "Comment":
1554                         case "Like":
1555                         case "Post": // "Post" will be supported in a future version
1556                         case "Reshare":
1557                         case "StatusMessage":
1558                                 return self::item_retraction($importer, $contact, $data);;
1559
1560                         case "Person": /// @todo an "unshare" shouldn't remove the contact
1561                                 contact_remove($contact["id"]);
1562                                 return true;
1563
1564                         default:
1565                                 logger("Unknown target type ".$target_type);
1566                                 return false;
1567                 }
1568                 return true;
1569         }
1570
1571         private function receive_status_message($importer, $data) {
1572
1573                 $raw_message = unxmlify($data->raw_message);
1574                 $guid = notags(unxmlify($data->guid));
1575                 $author = notags(unxmlify($data->author));
1576                 $public = notags(unxmlify($data->public));
1577                 $created_at = notags(unxmlify($data->created_at));
1578                 $provider_display_name = notags(unxmlify($data->provider_display_name));
1579
1580                 /// @todo enable support for polls
1581                 //if ($data->poll) {
1582                 //      foreach ($data->poll AS $poll)
1583                 //              print_r($poll);
1584                 //      die("poll!\n");
1585                 //}
1586                 $contact = self::get_allowed_contact_by_handle($importer, $author, false);
1587                 if (!$contact)
1588                         return false;
1589
1590                 if (self::message_exists($importer["uid"], $guid))
1591                         return false;
1592
1593                 $address = array();
1594                 if ($data->location)
1595                         foreach ($data->location->children() AS $fieldname => $data)
1596                                 $address[$fieldname] = notags(unxmlify($data));
1597
1598                 $body = diaspora2bb($raw_message);
1599
1600                 $datarray = array();
1601
1602                 if ($data->photo) {
1603                         foreach ($data->photo AS $photo)
1604                                 $body = "[img]".$photo->remote_photo_path.$photo->remote_photo_name."[/img]\n".$body;
1605
1606                         $datarray["object-type"] = ACTIVITY_OBJ_PHOTO;
1607                 } else {
1608                         $datarray["object-type"] = ACTIVITY_OBJ_NOTE;
1609
1610                         // Add OEmbed and other information to the body
1611                         if (!self::is_redmatrix($contact["url"]))
1612                                 $body = add_page_info_to_body($body, false, true);
1613                 }
1614
1615                 $datarray["uid"] = $importer["uid"];
1616                 $datarray["contact-id"] = $contact["id"];
1617                 $datarray["network"] = NETWORK_DIASPORA;
1618
1619                 $datarray["author-name"] = $contact["name"];
1620                 $datarray["author-link"] = $contact["url"];
1621                 $datarray["author-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]);
1622
1623                 $datarray["owner-name"] = $datarray["author-name"];
1624                 $datarray["owner-link"] = $datarray["author-link"];
1625                 $datarray["owner-avatar"] = $datarray["author-avatar"];
1626
1627                 $datarray["guid"] = $guid;
1628                 $datarray["uri"] = $datarray["parent-uri"] = $author.":".$guid;
1629
1630                 $datarray["verb"] = ACTIVITY_POST;
1631                 $datarray["gravity"] = GRAVITY_PARENT;
1632
1633                 $datarray["object"] = json_encode($data);
1634
1635                 $datarray["body"] = $body;
1636
1637                 if ($provider_display_name != "")
1638                         $datarray["app"] = $provider_display_name;
1639
1640                 $datarray["plink"] = self::plink($author, $guid);
1641                 $datarray["private"] = (($public == "false") ? 1 : 0);
1642                 $datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert("UTC", "UTC", $created_at);
1643
1644                 if (isset($address["address"]))
1645                         $datarray["location"] = $address["address"];
1646
1647                 if (isset($address["lat"]) AND isset($address["lng"]))
1648                         $datarray["coord"] = $address["lat"]." ".$address["lng"];
1649
1650                 self::fetch_guid($datarray);
1651                 $message_id = item_store($datarray);
1652                 // print_r($datarray);
1653
1654                 logger("Stored item with message id ".$message_id, LOGGER_DEBUG);
1655
1656                 return $message_id;
1657         }
1658
1659         /*******************************************************************************************
1660          * Here come all the functions that are needed to transmit data with the Diaspora protocol *
1661          *******************************************************************************************/
1662
1663         private function get_my_handle($me) {
1664                 if ($contact["addr"] != "")
1665                         return $contact["addr"];
1666
1667                 // Normally we should have a filled "addr" field - but in the past this wasn't the case
1668                 // So - just in case - we build the the address here.
1669                 return $me["nickname"]."@".substr(App::get_baseurl(), strpos(App::get_baseurl(),"://") + 3);
1670         }
1671
1672         private function build_public_message($msg, $user, $contact, $prvkey, $pubkey) {
1673
1674                 logger("Message: ".$msg, LOGGER_DATA);
1675
1676                 $handle = self::get_my_handle($user);
1677
1678                 $b64url_data = base64url_encode($msg);
1679
1680                 $data = str_replace(array("\n", "\r", " ", "\t"), array("", "", "", ""), $b64url_data);
1681
1682                 $type = "application/xml";
1683                 $encoding = "base64url";
1684                 $alg = "RSA-SHA256";
1685
1686                 $signable_data = $data.".".base64url_encode($type).".".base64url_encode($encoding).".".base64url_encode($alg);
1687
1688                 $signature = rsa_sign($signable_data,$prvkey);
1689                 $sig = base64url_encode($signature);
1690
1691 $magic_env = <<< EOT
1692 <?xml version='1.0' encoding='UTF-8'?>
1693 <diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env" >
1694   <header>
1695     <author_id>$handle</author_id>
1696   </header>
1697   <me:env>
1698     <me:encoding>base64url</me:encoding>
1699     <me:alg>RSA-SHA256</me:alg>
1700     <me:data type="application/xml">$data</me:data>
1701     <me:sig>$sig</me:sig>
1702   </me:env>
1703 </diaspora>
1704 EOT;
1705
1706                 logger("magic_env: ".$magic_env, LOGGER_DATA);
1707                 return $magic_env;
1708         }
1709
1710         private function build_private_message($msg, $user, $contact, $prvkey, $pubkey) {
1711
1712                 logger("Message: ".$msg, LOGGER_DATA);
1713
1714                 // without a public key nothing will work
1715
1716                 if (!$pubkey) {
1717                         logger("pubkey missing: contact id: ".$contact["id"]);
1718                         return false;
1719                 }
1720
1721                 $inner_aes_key = random_string(32);
1722                 $b_inner_aes_key = base64_encode($inner_aes_key);
1723                 $inner_iv = random_string(16);
1724                 $b_inner_iv = base64_encode($inner_iv);
1725
1726                 $outer_aes_key = random_string(32);
1727                 $b_outer_aes_key = base64_encode($outer_aes_key);
1728                 $outer_iv = random_string(16);
1729                 $b_outer_iv = base64_encode($outer_iv);
1730
1731                 $handle = self::get_my_handle($user);
1732
1733                 $padded_data = pkcs5_pad($msg,16);
1734                 $inner_encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $padded_data, MCRYPT_MODE_CBC, $inner_iv);
1735
1736                 $b64_data = base64_encode($inner_encrypted);
1737
1738
1739                 $b64url_data = base64url_encode($b64_data);
1740                 $data = str_replace(array("\n", "\r", " ", "\t"), array("", "", "", ""), $b64url_data);
1741
1742                 $type = "application/xml";
1743                 $encoding = "base64url";
1744                 $alg = "RSA-SHA256";
1745
1746                 $signable_data = $data.".".base64url_encode($type).".".base64url_encode($encoding).".".base64url_encode($alg);
1747
1748                 $signature = rsa_sign($signable_data,$prvkey);
1749                 $sig = base64url_encode($signature);
1750
1751 $decrypted_header = <<< EOT
1752 <decrypted_header>
1753   <iv>$b_inner_iv</iv>
1754   <aes_key>$b_inner_aes_key</aes_key>
1755   <author_id>$handle</author_id>
1756 </decrypted_header>
1757 EOT;
1758
1759                 $decrypted_header = pkcs5_pad($decrypted_header,16);
1760
1761                 $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $outer_aes_key, $decrypted_header, MCRYPT_MODE_CBC, $outer_iv);
1762
1763                 $outer_json = json_encode(array("iv" => $b_outer_iv, "key" => $b_outer_aes_key));
1764
1765                 $encrypted_outer_key_bundle = "";
1766                 openssl_public_encrypt($outer_json, $encrypted_outer_key_bundle, $pubkey);
1767
1768                 $b64_encrypted_outer_key_bundle = base64_encode($encrypted_outer_key_bundle);
1769
1770                 logger("outer_bundle: ".$b64_encrypted_outer_key_bundle." key: ".$pubkey, LOGGER_DATA);
1771
1772                 $encrypted_header_json_object = json_encode(array("aes_key" => base64_encode($encrypted_outer_key_bundle),
1773                                                                 "ciphertext" => base64_encode($ciphertext)));
1774                 $cipher_json = base64_encode($encrypted_header_json_object);
1775
1776                 $encrypted_header = "<encrypted_header>".$cipher_json."</encrypted_header>";
1777
1778 $magic_env = <<< EOT
1779 <?xml version='1.0' encoding='UTF-8'?>
1780 <diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env" >
1781   $encrypted_header
1782   <me:env>
1783     <me:encoding>base64url</me:encoding>
1784     <me:alg>RSA-SHA256</me:alg>
1785     <me:data type="application/xml">$data</me:data>
1786     <me:sig>$sig</me:sig>
1787   </me:env>
1788 </diaspora>
1789 EOT;
1790
1791                 logger("magic_env: ".$magic_env, LOGGER_DATA);
1792                 return $magic_env;
1793         }
1794
1795         private function build_message($msg, $user, $contact, $prvkey, $pubkey, $public = false) {
1796
1797                 if ($public)
1798                         $magic_env =  self::build_public_message($msg,$user,$contact,$prvkey,$pubkey);
1799                 else
1800                         $magic_env =  self::build_private_message($msg,$user,$contact,$prvkey,$pubkey);
1801
1802                 // The data that will be transmitted is double encoded via "urlencode", strange ...
1803                 $slap = "xml=".urlencode(urlencode($magic_env));
1804                 return $slap;
1805         }
1806
1807         private function get_signature($owner, $message) {
1808                 $sigmsg = $message;
1809                 unset($sigmsg["author_signature"]);
1810                 unset($sigmsg["parent_author_signature"]);
1811
1812                 $signed_text = implode(";", $sigmsg);
1813
1814                 return base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256"));
1815         }
1816
1817         private function transmit($owner, $contact, $slap, $public_batch, $queue_run=false, $guid = "") {
1818
1819                 $a = get_app();
1820
1821                 $enabled = intval(get_config("system", "diaspora_enabled"));
1822                 if(!$enabled)
1823                         return 200;
1824
1825                 $logid = random_string(4);
1826                 $dest_url = (($public_batch) ? $contact["batch"] : $contact["notify"]);
1827                 if (!$dest_url) {
1828                         logger("no url for contact: ".$contact["id"]." batch mode =".$public_batch);
1829                         return 0;
1830                 }
1831
1832                 logger("transmit: ".$logid."-".$guid." ".$dest_url);
1833
1834                 if (!$queue_run && was_recently_delayed($contact["id"])) {
1835                         $return_code = 0;
1836                 } else {
1837                         if (!intval(get_config("system", "diaspora_test"))) {
1838                                 post_url($dest_url."/", $slap);
1839                                 $return_code = $a->get_curl_code();
1840                         } else {
1841                                 logger("test_mode");
1842                                 return 200;
1843                         }
1844                 }
1845
1846                 logger("transmit: ".$logid."-".$guid." returns: ".$return_code);
1847
1848                 if(!$return_code || (($return_code == 503) && (stristr($a->get_curl_headers(), "retry-after")))) {
1849                         logger("queue message");
1850
1851                         $r = q("SELECT `id` FROM `queue` WHERE `cid` = %d AND `network` = '%s' AND `content` = '%s' AND `batch` = %d LIMIT 1",
1852                                 intval($contact["id"]),
1853                                 dbesc(NETWORK_DIASPORA),
1854                                 dbesc($slap),
1855                                 intval($public_batch)
1856                         );
1857                         if(count($r)) {
1858                                 logger("add_to_queue ignored - identical item already in queue");
1859                         } else {
1860                                 // queue message for redelivery
1861                                 add_to_queue($contact["id"], NETWORK_DIASPORA, $slap, $public_batch);
1862                         }
1863                 }
1864
1865
1866                 return(($return_code) ? $return_code : (-1));
1867         }
1868
1869         public static function send_share($me,$contact) {
1870                 $myaddr = self::get_my_handle($me);
1871                 $theiraddr = $contact["addr"];
1872
1873                 $data = array("XML" => array("post" => array("request" => array(
1874                                                                                 "sender_handle" => $myaddr,
1875                                                                                 "recipient_handle" => $theiraddr
1876                                                                         ))));
1877
1878                 $msg = xml::from_array($data, $xml);
1879
1880                 $slap = self::build_message($msg, $me, $contact, $me["prvkey"], $contact["pubkey"]);
1881
1882                 return(self::transmit($owner,$contact,$slap, false));
1883
1884         }
1885
1886         public static function send_unshare($me,$contact) {
1887                 $myaddr = self::get_my_handle($me);
1888
1889                 $data = array("XML" => array("post" => array("retraction" => array(
1890                                                                                 "post_guid" => $me["guid"],
1891                                                                                 "diaspora_handle" => $myaddr,
1892                                                                                 "type" => "Person"
1893                                                                         ))));
1894
1895                 $msg = xml::from_array($data, $xml);
1896
1897                 $slap = self::build_message($msg, $me, $contact, $me["prvkey"], $contact["pubkey"]);
1898
1899                 return(self::transmit($owner,$contact,$slap, false));
1900         }
1901
1902         private function is_reshare($body) {
1903                 $body = trim($body);
1904
1905                 // Skip if it isn't a pure repeated messages
1906                 // Does it start with a share?
1907                 if (strpos($body, "[share") > 0)
1908                         return(false);
1909
1910                 // Does it end with a share?
1911                 if (strlen($body) > (strrpos($body, "[/share]") + 8))
1912                         return(false);
1913
1914                 $attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism","$1",$body);
1915                 // Skip if there is no shared message in there
1916                 if ($body == $attributes)
1917                         return(false);
1918
1919                 $guid = "";
1920                 preg_match("/guid='(.*?)'/ism", $attributes, $matches);
1921                 if ($matches[1] != "")
1922                         $guid = $matches[1];
1923
1924                 preg_match('/guid="(.*?)"/ism', $attributes, $matches);
1925                 if ($matches[1] != "")
1926                         $guid = $matches[1];
1927
1928                 if ($guid != "") {
1929                         $r = q("SELECT `contact-id` FROM `item` WHERE `guid` = '%s' AND `network` IN ('%s', '%s') LIMIT 1",
1930                                 dbesc($guid), NETWORK_DFRN, NETWORK_DIASPORA);
1931                         if ($r) {
1932                                 $ret= array();
1933                                 $ret["root_handle"] = diaspora_handle_from_contact($r[0]["contact-id"]);
1934                                 $ret["root_guid"] = $guid;
1935                                 return($ret);
1936                         }
1937                 }
1938
1939                 $profile = "";
1940                 preg_match("/profile='(.*?)'/ism", $attributes, $matches);
1941                 if ($matches[1] != "")
1942                         $profile = $matches[1];
1943
1944                 preg_match('/profile="(.*?)"/ism', $attributes, $matches);
1945                 if ($matches[1] != "")
1946                         $profile = $matches[1];
1947
1948                 $ret= array();
1949
1950                 $ret["root_handle"] = preg_replace("=https?://(.*)/u/(.*)=ism", "$2@$1", $profile);
1951                 if (($ret["root_handle"] == $profile) OR ($ret["root_handle"] == ""))
1952                         return(false);
1953
1954                 $link = "";
1955                 preg_match("/link='(.*?)'/ism", $attributes, $matches);
1956                 if ($matches[1] != "")
1957                         $link = $matches[1];
1958
1959                 preg_match('/link="(.*?)"/ism', $attributes, $matches);
1960                 if ($matches[1] != "")
1961                         $link = $matches[1];
1962
1963                 $ret["root_guid"] = preg_replace("=https?://(.*)/posts/(.*)=ism", "$2", $link);
1964                 if (($ret["root_guid"] == $link) OR ($ret["root_guid"] == ""))
1965                         return(false);
1966                 return($ret);
1967         }
1968
1969         public static function send_status($item, $owner, $contact, $public_batch = false) {
1970
1971                 $myaddr = self::get_my_handle($owner);
1972                 $theiraddr = $contact["addr"];
1973
1974                 $title = $item["title"];
1975                 $body = $item["body"];
1976
1977                 // convert to markdown
1978                 $body = html_entity_decode(bb2diaspora($body));
1979
1980                 // Adding the title
1981                 if(strlen($title))
1982                         $body = "## ".html_entity_decode($title)."\n\n".$body;
1983
1984                 if ($item["attach"]) {
1985                         $cnt = preg_match_all('/href=\"(.*?)\"(.*?)title=\"(.*?)\"/ism', $item["attach"], $matches, PREG_SET_ORDER);
1986                         if(cnt) {
1987                                 $body .= "\n".t("Attachments:")."\n";
1988                                 foreach($matches as $mtch)
1989                                         $body .= "[".$mtch[3]."](".$mtch[1].")\n";
1990                         }
1991                 }
1992
1993
1994                 $public = (($item["private"]) ? "false" : "true");
1995
1996                 $created = datetime_convert("UTC", "UTC", $item["created"], 'Y-m-d H:i:s \U\T\C');
1997
1998                 // Detect a share element and do a reshare
1999                 if (!$item['private'] AND ($ret = self::is_reshare($item["body"]))) {
2000                         $message = array("root_diaspora_id" => $ret["root_handle"],
2001                                         "root_guid" => $ret["root_guid"],
2002                                         "guid" => $item["guid"],
2003                                         "diaspora_handle" => $myaddr,
2004                                         "public" => $public,
2005                                         "created_at" => $created,
2006                                         "provider_display_name" => $item["app"]);
2007
2008                         $data = array("XML" => array("post" => array("reshare" => $message)));
2009                 } else {
2010                         $location = array();
2011
2012                         if ($item["location"] != "")
2013                                 $location["address"] = $item["location"];
2014
2015                         if ($item["coord"] != "") {
2016                                 $coord = explode(" ", $item["coord"]);
2017                                 $location["lat"] = $coord[0];
2018                                 $location["lng"] = $coord[1];
2019                         }
2020
2021                         $message = array("raw_message" => $body,
2022                                         "location" => $location,
2023                                         "guid" => $item["guid"],
2024                                         "diaspora_handle" => $myaddr,
2025                                         "public" => $public,
2026                                         "created_at" => $created,
2027                                         "provider_display_name" => $item["app"]);
2028
2029                         if (count($location) == 0)
2030                                 unset($message["location"]);
2031
2032                         $data = array("XML" => array("post" => array("status_message" => $message)));
2033                 }
2034
2035                 $msg = xml::from_array($data, $xml);
2036
2037                 logger("status: ".$owner["username"]." -> ".$contact["name"]." base message: ".$msg, LOGGER_DATA);
2038                 logger("send guid ".$item["guid"], LOGGER_DEBUG);
2039
2040                 $slap = self::build_message($msg, $owner, $contact, $owner["uprvkey"], $contact["pubkey"], $public_batch);
2041
2042                 $return_code = self::transmit($owner,$contact,$slap, false);
2043
2044                 logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG);
2045
2046                 return $return_code;
2047         }
2048
2049         private function construct_like($item,$owner,$contact,$public_batch = false, $data = null) {
2050
2051                 if (is_array($data))
2052                         $message = $data;
2053                 else {
2054                         $myaddr = self::get_my_handle($owner);
2055
2056                         $p = q("SELECT `guid`, `uri`, `parent-uri` FROM `item` WHERE `uri` = '%s' LIMIT 1",
2057                                 dbesc($item["thr-parent"]));
2058                         if(!$p)
2059                                 return false;
2060
2061                         $parent = $p[0];
2062
2063                         $target_type = ($parent["uri"] === $parent["parent-uri"] ? "Post" : "Comment");
2064                         $positive = "true";
2065
2066                         $message = array("positive" => $positive,
2067                                         "guid" => $item["guid"],
2068                                         "target_type" => $target_type,
2069                                         "parent_guid" => $parent["guid"],
2070                                         "author_signature" => $authorsig,
2071                                         "diaspora_handle" => $myaddr);
2072                 }
2073
2074                 $authorsig = self::get_signature($owner, $message);
2075
2076                 if ($message["author_signature"] == "")
2077                         $message["author_signature"] = $authorsig;
2078                 else
2079                         $message["parent_author_signature"] = $authorsig;
2080
2081                 $data = array("XML" => array("post" => array("like" => $message)));
2082
2083                 return xml::from_array($data, $xml);
2084         }
2085
2086         private function construct_comment($item,$owner,$contact,$public_batch = false, $data = null) {
2087
2088                 if (is_array($data))
2089                         $message = $data;
2090                 else {
2091                         $myaddr = self::get_my_handle($owner);
2092
2093                         $p = q("SELECT `guid` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1",
2094                                 intval($item["parent"]),
2095                                 intval($item["parent"])
2096                         );
2097
2098                         if (!$p)
2099                                 return false;
2100
2101                         $parent = $p[0];
2102
2103                         $text = html_entity_decode(bb2diaspora($item["body"]));
2104
2105                         $message = array("guid" => $item["guid"],
2106                                         "parent_guid" => $parent["guid"],
2107                                         "author_signature" => "",
2108                                         "text" => $text,
2109                                         "diaspora_handle" => $myaddr);
2110                 }
2111
2112                 $authorsig = self::get_signature($owner, $message);
2113
2114                 if ($message["author_signature"] == "")
2115                         $message["author_signature"] = $authorsig;
2116                 else
2117                         $message["parent_author_signature"] = $authorsig;
2118
2119                 $data = array("XML" => array("post" => array("comment" => $message)));
2120
2121                 return xml::from_array($data, $xml);
2122         }
2123
2124         public static function send_followup($item,$owner,$contact,$public_batch = false) {
2125
2126                 if($item['verb'] === ACTIVITY_LIKE)
2127                         $msg = self::construct_like($item, $owner, $contact, $public_batch);
2128                 else
2129                         $msg = self::construct_comment($item, $owner, $contact, $public_batch);
2130
2131                 if (!$msg)
2132                         return $msg;
2133
2134                 logger("base message: ".$msg, LOGGER_DATA);
2135                 logger("send guid ".$item["guid"], LOGGER_DEBUG);
2136
2137                 $slap = self::build_message($msg, $owner, $contact, $owner["uprvkey"], $contact["pubkey"], $public_batch);
2138
2139                 $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $item["guid"]);
2140
2141                 logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG);
2142
2143                 return $return_code;
2144         }
2145
2146         function send_relay($item, $owner, $contact, $public_batch = false) {
2147
2148                 if ($item["deleted"])
2149                         $sql_sign_id = "retract_iid";
2150                 else
2151                         $sql_sign_id = "iid";
2152
2153                 // fetch the original signature if the relayable was created by a Diaspora
2154                 // or DFRN user.
2155
2156                 $r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE `".$sql_sign_id."` = %d LIMIT 1",
2157                         intval($item["id"])
2158                 );
2159
2160                 if(count($r)) {
2161                         $orig_sign = $r[0];
2162                         $signed_text = $orig_sign['signed_text'];
2163                         $authorsig = $orig_sign['signature'];
2164                         $handle = $orig_sign['signer'];
2165
2166                         // Split the signed text
2167                         $signed_parts = explode(";", $signed_text);
2168
2169                         if ($item['verb'] === ACTIVITY_LIKE) {
2170                                 $data = array("positive" => $signed_parts[0],
2171                                                 "guid" => $signed_parts[1],
2172                                                 "target_type" => $signed_parts[2],
2173                                                 "parent_guid" => $signed_parts[3],
2174                                                 "parent_author_signature" => "",
2175                                                 "author_signature" => $orig_sign['signature'],
2176                                                 "diaspora_handle" => $signed_parts[4]);
2177                         } else {
2178                                 // Remove the comment guid
2179                                 $guid = array_shift($signed_parts);
2180
2181                                 // Remove the parent guid
2182                                 $parent_guid = array_shift($signed_parts);
2183
2184                                 // Remove the handle
2185                                 $handle = array_pop($signed_parts);
2186
2187                                 // Glue the parts together
2188                                 $text = implode(";", $signed_parts);
2189
2190                                 $data = array("guid" => $guid,
2191                                                 "parent_guid" => $parent_guid,
2192                                                 "parent_author_signature" => "",
2193                                                 "author_signature" => $orig_sign['signature'],
2194                                                 "text" => implode(";", $signed_parts),
2195                                                 "diaspora_handle" => $handle);
2196                         }
2197                 }
2198
2199                 if ($item['deleted'])
2200                         ; // Relayed Retraction
2201                 elseif($item['verb'] === ACTIVITY_LIKE)
2202                         $msg = self::construct_like($item, $owner, $contact, $public_batch, $data);
2203                 else
2204                         $msg = self::construct_comment($item, $owner, $contact, $public_batch, $data);
2205 die($msg);
2206
2207                 logger('base message: '.$msg, LOGGER_DATA);
2208                 logger('send guid '.$item['guid'], LOGGER_DEBUG);
2209
2210                 $slap = self::build_message($msg,$owner, $contact, $owner['uprvkey'], $contact['pubkey'], $public_batch);
2211
2212                 $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $item['guid']);
2213
2214                 logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG);
2215
2216                 return $return_code;
2217         }
2218
2219 /*
2220         // Diaspora doesn't support threaded comments, but some
2221         // versions of Diaspora (i.e. Diaspora-pistos) support
2222         // likes on comments
2223         if($item['verb'] === ACTIVITY_LIKE && $item['thr-parent']) {
2224                 $p = q("select guid, type, uri, `parent-uri` from item where uri = '%s' limit 1",
2225                         dbesc($item['thr-parent'])
2226                       );
2227         }
2228         else {
2229                 // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always
2230                 // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent.
2231                 // The only item with `parent` and `id` as the parent id is the parent item.
2232                 $p = q("select guid, type, uri, `parent-uri` from item where parent = %d and id = %d limit 1",
2233                        intval($item['parent']),
2234                        intval($item['parent'])
2235                       );
2236         }
2237         if(count($p))
2238                 $parent = $p[0];
2239         else
2240                 return;
2241
2242         $like = false;
2243         $relay_retract = false;
2244         $sql_sign_id = 'iid';
2245         if( $item['deleted']) {
2246                 $relay_retract = true;
2247
2248                 $target_type = ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
2249
2250                 $sql_sign_id = 'retract_iid';
2251                 $tpl = get_markup_template('diaspora_relayable_retraction.tpl');
2252         }
2253         elseif($item['verb'] === ACTIVITY_LIKE) {
2254                 $like = true;
2255
2256                 $target_type = ( $parent['uri'] === $parent['parent-uri']  ? 'Post' : 'Comment');
2257 //              $positive = (($item['deleted']) ? 'false' : 'true');
2258                 $positive = 'true';
2259
2260                 $tpl = get_markup_template('diaspora_like_relay.tpl');
2261         }
2262         else { // item is a comment
2263                 $tpl = get_markup_template('diaspora_comment_relay.tpl');
2264         }
2265
2266
2267         // fetch the original signature if the relayable was created by a Diaspora
2268         // or DFRN user. Relayables for other networks are not supported.
2269
2270         $r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE " . $sql_sign_id . " = %d LIMIT 1",
2271                 intval($item['id'])
2272         );
2273         if(count($r)) {
2274                 $orig_sign = $r[0];
2275                 $signed_text = $orig_sign['signed_text'];
2276                 $authorsig = $orig_sign['signature'];
2277                 $handle = $orig_sign['signer'];
2278
2279                 // Split the signed text
2280                 $signed_parts = explode(";", $signed_text);
2281
2282                 // Remove the parent guid
2283                 array_shift($signed_parts);
2284
2285                 // Remove the comment guid
2286                 array_shift($signed_parts);
2287
2288                 // Remove the handle
2289                 array_pop($signed_parts);
2290
2291                 // Glue the parts together
2292                 $text = implode(";", $signed_parts);
2293         }
2294         else {
2295                 // This part is meant for cases where we don't have the signatur. (Which shouldn't happen with posts from Diaspora and Friendica)
2296                 // This means that the comment won't be accepted by newer Diaspora servers
2297
2298                 $body = $item['body'];
2299                 $text = html_entity_decode(bb2diaspora($body));
2300
2301                 $handle = diaspora_handle_from_contact($item['contact-id']);
2302                 if(! $handle)
2303                         return;
2304
2305                 if($relay_retract)
2306                         $signed_text = $item['guid'] . ';' . $target_type;
2307                 elseif($like)
2308                         $signed_text = $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $positive . ';' . $handle;
2309                 else
2310                         $signed_text = $item['guid'] . ';' . $parent['guid'] . ';' . $text . ';' . $handle;
2311
2312                 $authorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'));
2313         }
2314
2315         // Sign the relayable with the top-level owner's signature
2316         $parentauthorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'));
2317
2318         $msg = replace_macros($tpl,array(
2319                 '$guid' => xmlify($item['guid']),
2320                 '$parent_guid' => xmlify($parent['guid']),
2321                 '$target_type' =>xmlify($target_type),
2322                 '$authorsig' => xmlify($authorsig),
2323                 '$parentsig' => xmlify($parentauthorsig),
2324                 '$body' => xmlify($text),
2325                 '$positive' => xmlify($positive),
2326                 '$handle' => xmlify($handle)
2327         ));
2328
2329         logger('diaspora_send_relay: base message: ' . $msg, LOGGER_DATA);
2330         logger('send guid '.$item['guid'], LOGGER_DEBUG);
2331
2332         $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)));
2333         //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch));
2334
2335         return(diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid']));
2336 */
2337
2338         public static function send_retraction($item, $owner, $contact, $public_batch = false) {
2339
2340                 $myaddr = self::get_my_handle($owner);
2341
2342                 // Check whether the retraction is for a top-level post or whether it's a relayable
2343                 if ($item["uri"] !== $item["parent-uri"]) {
2344                         $msg_type = "relayable_retraction";
2345                         $target_type = (($item["verb"] === ACTIVITY_LIKE) ? "Like" : "Comment");
2346                 } else {
2347                         $msg_type = "signed_retraction";
2348                         $target_type = "StatusMessage";
2349                 }
2350
2351                 $signed_text = $item["guid"].";".$target_type;
2352
2353                 $message = array("target_guid" => $item['guid'],
2354                                 "target_type" => $target_type,
2355                                 "sender_handle" => $myaddr,
2356                                 "target_author_signature" => base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')));
2357
2358                 $data = array("XML" => array("post" => array($msg_type => $message)));
2359                 $msg = xml::from_array($data, $xml);
2360
2361                 logger("send guid ".$item["guid"], LOGGER_DEBUG);
2362
2363                 $slap = self::build_message($msg, $owner, $contact, $owner["uprvkey"], $contact["pubkey"], $public_batch);
2364
2365                 $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $item["guid"]);
2366
2367                 logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG);
2368
2369                 return $return_code;
2370         }
2371
2372         public static function send_mail($item,$owner,$contact) {
2373
2374                 $myaddr = self::get_my_handle($owner);
2375
2376                 $r = q("SELECT * FROM `conv` WHERE `id` = %d AND `uid` = %d LIMIT 1",
2377                         intval($item["convid"]),
2378                         intval($item["uid"])
2379                 );
2380
2381                 if (!count($r)) {
2382                         logger("conversation not found.");
2383                         return;
2384                 }
2385                 $cnv = $r[0];
2386
2387                 $conv = array(
2388                         "guid" => $cnv["guid"],
2389                         "subject" => $cnv["subject"],
2390                         "created_at" => datetime_convert("UTC", "UTC", $cnv['created'], 'Y-m-d H:i:s \U\T\C'),
2391                         "diaspora_handle" => $cnv["creator"],
2392                         "participant_handles" => $cnv["recips"]
2393                 );
2394
2395                 $body = bb2diaspora($item["body"]);
2396                 $created = datetime_convert("UTC", "UTC", $item["created"], 'Y-m-d H:i:s \U\T\C');
2397
2398                 $signed_text = $item["guid"].";".$cnv["guid"].";".$body.";".$created.";".$myaddr.";".$cnv['guid'];
2399
2400                 $sig = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256"));
2401
2402                 $msg = array(
2403                         "guid" => $item["guid"],
2404                         "parent_guid" => $cnv["guid"],
2405                         "parent_author_signature" => $sig,
2406                         "author_signature" => $sig,
2407                         "text" => $body,
2408                         "created_at" => $created,
2409                         "diaspora_handle" => $myaddr,
2410                         "conversation_guid" => $cnv["guid"]
2411                 );
2412
2413                 if ($item["reply"])
2414                         $data = array("XML" => array("post" => array("message" => $msg)));
2415                 else {
2416                         $message = array("guid" => $cnv["guid"],
2417                                         "subject" => $cnv["subject"],
2418                                         "created_at" => datetime_convert("UTC", "UTC", $cnv['created'], 'Y-m-d H:i:s \U\T\C'),
2419                                         "message" => $msg,
2420                                         "diaspora_handle" => $cnv["creator"],
2421                                         "participant_handles" => $cnv["recips"]);
2422
2423                         $data = array("XML" => array("post" => array("conversation" => $message)));
2424                 }
2425
2426                 $xmsg = xml::from_array($data, $xml);
2427
2428                 logger("conversation: ".print_r($xmsg,true), LOGGER_DATA);
2429                 logger("send guid ".$item["guid"], LOGGER_DEBUG);
2430
2431                 $slap = self::build_message($xmsg, $owner, $contact, $owner["uprvkey"], $contact["pubkey"], false);
2432
2433                 $return_code = self::transmit($owner, $contact, $slap, false, false, $item["guid"]);
2434
2435                 logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG);
2436
2437                 return $return_code;
2438         }
2439 }
2440 ?>