]> git.mxchange.org Git - friendica.git/blob - include/ostatus.php
OStatus: Make sure that mentions are always accepted
[friendica.git] / include / ostatus.php
1 <?php
2 require_once("include/Contact.php");
3 require_once("include/threads.php");
4 require_once("include/html2bbcode.php");
5 require_once("include/items.php");
6 require_once("mod/share.php");
7 require_once("include/enotify.php");
8 require_once("include/socgraph.php");
9 require_once("include/Photo.php");
10 require_once("include/Scrape.php");
11 require_once("include/follow.php");
12 require_once("mod/proxy.php");
13
14 define('OSTATUS_DEFAULT_POLL_INTERVAL', 30); // given in minutes
15 define('OSTATUS_DEFAULT_POLL_TIMEFRAME', 1440); // given in minutes
16 define('OSTATUS_DEFAULT_POLL_TIMEFRAME_MENTIONS', 14400); // given in minutes
17
18 define("NS_ATOM", "http://www.w3.org/2005/Atom");
19 define("NS_THR", "http://purl.org/syndication/thread/1.0");
20 define("NS_GEORSS", "http://www.georss.org/georss");
21 define("NS_ACTIVITY", "http://activitystrea.ms/spec/1.0/");
22 define("NS_MEDIA", "http://purl.org/syndication/atommedia");
23 define("NS_POCO", "http://portablecontacts.net/spec/1.0");
24 define("NS_OSTATUS", "http://ostatus.org/schema/1.0");
25 define("NS_STATUSNET", "http://status.net/schema/api/1/");
26
27 function ostatus_check_follow_friends() {
28         $r = q("SELECT `uid`,`v` FROM `pconfig` WHERE `cat`='system' AND `k`='ostatus_legacy_contact' AND `v` != ''");
29
30         if (!$r)
31                 return;
32
33         foreach ($r AS $contact) {
34                 ostatus_follow_friends($contact["uid"], $contact["v"]);
35                 set_pconfig($contact["uid"], "system", "ostatus_legacy_contact", "");
36         }
37 }
38
39 // This function doesn't work reliable by now.
40 function ostatus_follow_friends($uid, $url) {
41         $contact = probe_url($url);
42
43         if (!$contact)
44                 return;
45
46         $api = $contact["baseurl"]."/api/";
47
48         // Fetching friends
49         $data = z_fetch_url($api."statuses/friends.json?screen_name=".$contact["nick"]);
50
51         if (!$data["success"])
52                 return;
53
54         $friends = json_decode($data["body"]);
55
56         foreach ($friends AS $friend) {
57                 $url = $friend->statusnet_profile_url;
58                 $r = q("SELECT `url` FROM `contact` WHERE `uid` = %d AND
59                         (`nurl` = '%s' OR `alias` = '%s' OR `alias` = '%s') AND
60                         `network` != '%s' LIMIT 1",
61                         intval($uid), dbesc(normalise_link($url)),
62                         dbesc(normalise_link($url)), dbesc($url), dbesc(NETWORK_STATUSNET));
63                 if (!$r) {
64                         $data = probe_url($friend->statusnet_profile_url);
65                         if ($data["network"] == NETWORK_OSTATUS) {
66                                 $result = new_contact($uid,$friend->statusnet_profile_url);
67                                 if ($result["success"])
68                                         logger($friend->name." ".$url." - success", LOGGER_DEBUG);
69                                 else
70                                         logger($friend->name." ".$url." - failed", LOGGER_DEBUG);
71                         } else
72                                 logger($friend->name." ".$url." - not OStatus", LOGGER_DEBUG);
73                 }
74         }
75 }
76
77 function ostatus_fetchauthor($xpath, $context, $importer, &$contact, $onlyfetch) {
78
79         $author = array();
80         $author["author-link"] = $xpath->evaluate('atom:author/atom:uri/text()', $context)->item(0)->nodeValue;
81         $author["author-name"] = $xpath->evaluate('atom:author/atom:name/text()', $context)->item(0)->nodeValue;
82
83         // Preserve the value
84         $authorlink = $author["author-link"];
85
86         $alternate = $xpath->query("atom:author/atom:link[@rel='alternate']", $context)->item(0)->attributes;
87         if (is_object($alternate))
88                 foreach($alternate AS $attributes)
89                         if ($attributes->name == "href")
90                                 $author["author-link"] = $attributes->textContent;
91
92         $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `nurl` IN ('%s', '%s') AND `network` != '%s'",
93                 intval($importer["uid"]), dbesc(normalise_link($author["author-link"])),
94                 dbesc(normalise_link($authorlink)), dbesc(NETWORK_STATUSNET));
95         if ($r) {
96                 $contact = $r[0];
97                 $author["contact-id"] = $r[0]["id"];
98         } else
99                 $author["contact-id"] = $contact["id"];
100
101         $avatarlist = array();
102         $avatars = $xpath->query("atom:author/atom:link[@rel='avatar']", $context);
103         foreach($avatars AS $avatar) {
104                 $href = "";
105                 $width = 0;
106                 foreach($avatar->attributes AS $attributes) {
107                         if ($attributes->name == "href")
108                                 $href = $attributes->textContent;
109                         if ($attributes->name == "width")
110                                 $width = $attributes->textContent;
111                 }
112                 if (($width > 0) AND ($href != ""))
113                         $avatarlist[$width] = $href;
114         }
115         if (count($avatarlist) > 0) {
116                 krsort($avatarlist);
117                 $author["author-avatar"] = current($avatarlist);
118         }
119
120         $displayname = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue;
121         if ($displayname != "")
122                 $author["author-name"] = $displayname;
123
124         $author["owner-name"] = $author["author-name"];
125         $author["owner-link"] = $author["author-link"];
126         $author["owner-avatar"] = $author["author-avatar"];
127
128         if ($r AND !$onlyfetch) {
129                 // Update contact data
130                 $update_contact = ($r[0]['name-date'] < datetime_convert('','','now -12 hours'));
131                 if ($update_contact) {
132                         logger("Update contact data for contact ".$contact["id"], LOGGER_DEBUG);
133
134                         $value = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue;
135                         if ($value != "")
136                                 $contact["name"] = $value;
137
138                         $value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $context)->item(0)->nodeValue;
139                         if ($value != "")
140                                 $contact["nick"] = $value;
141
142                         $value = $xpath->evaluate('atom:author/poco:note/text()', $context)->item(0)->nodeValue;
143                         if ($value != "")
144                                 $contact["about"] = $value;
145
146                         $value = $xpath->evaluate('atom:author/poco:address/poco:formatted/text()', $context)->item(0)->nodeValue;
147                         if ($value != "")
148                                 $contact["location"] = $value;
149
150                         q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `about` = '%s', `location` = '%s', `name-date` = '%s' WHERE `id` = %d AND `network` = '%s'",
151                                 dbesc($contact["name"]), dbesc($contact["nick"]), dbesc($contact["about"]), dbesc($contact["location"]),
152                                 dbesc(datetime_convert()), intval($contact["id"]), dbesc(NETWORK_OSTATUS));
153
154                         poco_check($contact["url"], $contact["name"], $contact["network"], $author["author-avatar"], $contact["about"], $contact["location"],
155                                         "", "", "", datetime_convert(), 2, $contact["id"], $contact["uid"]);
156                 }
157
158                 $update_photo = ($r[0]['avatar-date'] < datetime_convert('','','now -12 hours'));
159
160                 if ($update_photo AND isset($author["author-avatar"])) {
161                         logger("Update profile picture for contact ".$contact["id"], LOGGER_DEBUG);
162
163                         $photos = import_profile_photo($author["author-avatar"], $importer["uid"], $contact["id"]);
164
165                         q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s', `avatar-date` = '%s' WHERE `id` = %d AND `network` = '%s'",
166                                 dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]),
167                                 dbesc(datetime_convert()), intval($contact["id"]), dbesc(NETWORK_OSTATUS));
168                 }
169         }
170
171         return($author);
172 }
173
174 function ostatus_salmon_author($xml, $importer) {
175         $a = get_app();
176
177         if ($xml == "")
178                 return;
179
180         $doc = new DOMDocument();
181         @$doc->loadXML($xml);
182
183         $xpath = new DomXPath($doc);
184         $xpath->registerNamespace('atom', "http://www.w3.org/2005/Atom");
185         $xpath->registerNamespace('thr', "http://purl.org/syndication/thread/1.0");
186         $xpath->registerNamespace('georss', "http://www.georss.org/georss");
187         $xpath->registerNamespace('activity', "http://activitystrea.ms/spec/1.0/");
188         $xpath->registerNamespace('media', "http://purl.org/syndication/atommedia");
189         $xpath->registerNamespace('poco', "http://portablecontacts.net/spec/1.0");
190         $xpath->registerNamespace('ostatus', "http://ostatus.org/schema/1.0");
191         $xpath->registerNamespace('statusnet', "http://status.net/schema/api/1/");
192
193         $entries = $xpath->query('/atom:entry');
194
195         foreach ($entries AS $entry) {
196                 // fetch the author
197                 $author = ostatus_fetchauthor($xpath, $entry, $importer, $contact, true);
198                 return $author;
199         }
200 }
201
202 function ostatus_import($xml,$importer,&$contact, &$hub) {
203
204         $a = get_app();
205
206         logger("Import OStatus message", LOGGER_DEBUG);
207
208         if ($xml == "")
209                 return;
210
211         $doc = new DOMDocument();
212         @$doc->loadXML($xml);
213
214         $xpath = new DomXPath($doc);
215         $xpath->registerNamespace('atom', "http://www.w3.org/2005/Atom");
216         $xpath->registerNamespace('thr', "http://purl.org/syndication/thread/1.0");
217         $xpath->registerNamespace('georss', "http://www.georss.org/georss");
218         $xpath->registerNamespace('activity', "http://activitystrea.ms/spec/1.0/");
219         $xpath->registerNamespace('media', "http://purl.org/syndication/atommedia");
220         $xpath->registerNamespace('poco', "http://portablecontacts.net/spec/1.0");
221         $xpath->registerNamespace('ostatus', "http://ostatus.org/schema/1.0");
222         $xpath->registerNamespace('statusnet', "http://status.net/schema/api/1/");
223
224         $gub = "";
225         $hub_attributes = $xpath->query("/atom:feed/atom:link[@rel='hub']")->item(0)->attributes;
226         if (is_object($hub_attributes))
227                 foreach($hub_attributes AS $hub_attribute)
228                         if ($hub_attribute->name == "href") {
229                                 $hub = $hub_attribute->textContent;
230                                 logger("Found hub ".$hub, LOGGER_DEBUG);
231                         }
232
233         $header = array();
234         $header["uid"] = $importer["uid"];
235         $header["network"] = NETWORK_OSTATUS;
236         $header["type"] = "remote";
237         $header["wall"] = 0;
238         $header["origin"] = 0;
239         $header["gravity"] = GRAVITY_PARENT;
240
241         // it could either be a received post or a post we fetched by ourselves
242         // depending on that, the first node is different
243         $first_child = $doc->firstChild->tagName;
244
245         if ($first_child == "feed")
246                 $entries = $xpath->query('/atom:feed/atom:entry');
247         else
248                 $entries = $xpath->query('/atom:entry');
249
250         $conversation = "";
251         $conversationlist = array();
252         $item_id = 0;
253
254         // Reverse the order of the entries
255         $entrylist = array();
256
257         foreach ($entries AS $entry)
258                 $entrylist[] = $entry;
259
260         foreach (array_reverse($entrylist) AS $entry) {
261
262                 $mention = false;
263
264                 // fetch the author
265                 if ($first_child == "feed")
266                         $author = ostatus_fetchauthor($xpath, $doc->firstChild, $importer, $contact, false);
267                 else
268                         $author = ostatus_fetchauthor($xpath, $entry, $importer, $contact, false);
269
270                 $value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $context)->item(0)->nodeValue;
271                 if ($value != "")
272                         $nickname = $value;
273                 else
274                         $nickname = $author["author-name"];
275
276                 $item = array_merge($header, $author);
277
278                 // Now get the item
279                 $item["uri"] = $xpath->query('atom:id/text()', $entry)->item(0)->nodeValue;
280
281                 $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'",
282                         intval($importer["uid"]), dbesc($item["uri"]));
283                 if ($r) {
284                         logger("Item with uri ".$item["uri"]." for user ".$importer["uid"]." already existed under id ".$r[0]["id"], LOGGER_DEBUG);
285                         continue;
286                 }
287
288                 $item["body"] = add_page_info_to_body(html2bbcode($xpath->query('atom:content/text()', $entry)->item(0)->nodeValue));
289                 $item["object-type"] = $xpath->query('activity:object-type/text()', $entry)->item(0)->nodeValue;
290
291                 if (($item["object-type"] == ACTIVITY_OBJ_BOOKMARK) OR ($item["object-type"] == ACTIVITY_OBJ_EVENT)) {
292                         $item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue;
293                         $item["body"] = $xpath->query('atom:summary/text()', $entry)->item(0)->nodeValue;
294                 } elseif ($item["object-type"] == ACTIVITY_OBJ_QUESTION)
295                         $item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue;
296
297                 $item["object"] = $xml;
298                 $item["verb"] = $xpath->query('activity:verb/text()', $entry)->item(0)->nodeValue;
299
300                 // To-Do:
301                 // Delete a message
302                 if ($item["verb"] == "qvitter-delete-notice") {
303                         // ignore "Delete" messages (by now)
304                         logger("Ignore delete message ".print_r($item, true));
305                         continue;
306                 }
307
308                 if ($item["verb"] == ACTIVITY_JOIN) {
309                         // ignore "Join" messages
310                         logger("Ignore join message ".print_r($item, true));
311                         continue;
312                 }
313
314                 if ($item["verb"] == ACTIVITY_FOLLOW) {
315                         new_follower($importer, $contact, $item, $nickname);
316                         continue;
317                 }
318
319                 if ($item["verb"] == NAMESPACE_OSTATUS."/unfollow") {
320                         lose_follower($importer, $contact, $item, $dummy);
321                         continue;
322                 }
323
324                 if ($item["verb"] == ACTIVITY_FAVORITE) {
325                         $orig_uri = $xpath->query("activity:object/atom:id", $entry)->item(0)->nodeValue;
326                         logger("Favorite ".$orig_uri." ".print_r($item, true));
327
328                         $item["verb"] = ACTIVITY_LIKE;
329                         $item["parent-uri"] = $orig_uri;
330                         $item["gravity"] = GRAVITY_LIKE;
331                 }
332
333                 if ($item["verb"] == NAMESPACE_OSTATUS."/unfavorite") {
334                         // Ignore "Unfavorite" message
335                         logger("Ignore unfavorite message ".print_r($item, true));
336                         continue;
337                 }
338
339                 // http://activitystrea.ms/schema/1.0/rsvp-yes
340                 if (!in_array($item["verb"], array(ACTIVITY_POST, ACTIVITY_LIKE, ACTIVITY_SHARE)))
341                         logger("Unhandled verb ".$item["verb"]." ".print_r($item, true));
342
343                 $item["created"] = $xpath->query('atom:published/text()', $entry)->item(0)->nodeValue;
344                 $item["edited"] = $xpath->query('atom:updated/text()', $entry)->item(0)->nodeValue;
345                 $conversation = $xpath->query('ostatus:conversation/text()', $entry)->item(0)->nodeValue;
346
347                 $related = "";
348
349                 $inreplyto = $xpath->query('thr:in-reply-to', $entry);
350                 if (is_object($inreplyto->item(0))) {
351                         foreach($inreplyto->item(0)->attributes AS $attributes) {
352                                 if ($attributes->name == "ref")
353                                         $item["parent-uri"] = $attributes->textContent;
354                                 if ($attributes->name == "href")
355                                         $related = $attributes->textContent;
356                         }
357                 }
358
359                 $georsspoint = $xpath->query('georss:point', $entry);
360                 if ($georsspoint)
361                         $item["coord"] = $georsspoint->item(0)->nodeValue;
362
363                 // To-Do
364                 // $item["location"] =
365
366                 $categories = $xpath->query('atom:category', $entry);
367                 if ($categories) {
368                         foreach ($categories AS $category) {
369                                 foreach($category->attributes AS $attributes)
370                                         if ($attributes->name == "term") {
371                                                 $term = $attributes->textContent;
372                                                 if(strlen($item["tag"]))
373                                                         $item["tag"] .= ',';
374                                                 $item["tag"] .= "#[url=".$a->get_baseurl()."/search?tag=".$term."]".$term."[/url]";
375                                         }
376                         }
377                 }
378
379                 $self = "";
380                 $enclosure = "";
381
382                 $links = $xpath->query('atom:link', $entry);
383                 if ($links) {
384                         $rel = "";
385                         $href = "";
386                         $type = "";
387                         $length = "0";
388                         $title = "";
389                         foreach ($links AS $link) {
390                                 foreach($link->attributes AS $attributes) {
391                                         if ($attributes->name == "href")
392                                                 $href = $attributes->textContent;
393                                         if ($attributes->name == "rel")
394                                                 $rel = $attributes->textContent;
395                                         if ($attributes->name == "type")
396                                                 $type = $attributes->textContent;
397                                         if ($attributes->name == "length")
398                                                 $length = $attributes->textContent;
399                                         if ($attributes->name == "title")
400                                                 $title = $attributes->textContent;
401                                 }
402                                 if (($rel != "") AND ($href != ""))
403                                         switch($rel) {
404                                                 case "alternate":
405                                                         $item["plink"] = $href;
406                                                         if (($item["object-type"] == ACTIVITY_OBJ_QUESTION) OR
407                                                                 ($item["object-type"] == ACTIVITY_OBJ_EVENT))
408                                                                 $item["body"] .= add_page_info($href);
409                                                         break;
410                                                 case "ostatus:conversation":
411                                                         $conversation = $href;
412                                                         break;
413                                                 case "enclosure":
414                                                         $enclosure = $href;
415                                                         if(strlen($item["attach"]))
416                                                                 $item["attach"] .= ',';
417
418                                                         $item["attach"] .= '[attach]href="'.$href.'" length="'.$length.'" type="'.$type.'" title="'.$title.'"[/attach]';
419                                                         break;
420                                                 case "related":
421                                                         if ($item["object-type"] != ACTIVITY_OBJ_BOOKMARK) {
422                                                                 if (!isset($item["parent-uri"]))
423                                                                         $item["parent-uri"] = $href;
424
425                                                                 if ($related == "")
426                                                                         $related = $href;
427                                                         } else
428                                                                 $item["body"] .= add_page_info($href);
429                                                         break;
430                                                 case "self":
431                                                         $self = $href;
432                                                         break;
433                                                 case "mentioned":
434                                                         // Notification check
435                                                         if ($importer["nurl"] == normalise_link($href))
436                                                                 $mention = true;
437                                                         break;
438                                         }
439                         }
440                 }
441
442                 $local_id = "";
443                 $repeat_of = "";
444
445                 $notice_info = $xpath->query('statusnet:notice_info', $entry);
446                 if ($notice_info AND ($notice_info->length > 0)) {
447                         foreach($notice_info->item(0)->attributes AS $attributes) {
448                                 if ($attributes->name == "source")
449                                         $item["app"] = strip_tags($attributes->textContent);
450                                 if ($attributes->name == "local_id")
451                                         $local_id = $attributes->textContent;
452                                 if ($attributes->name == "repeat_of")
453                                         $repeat_of = $attributes->textContent;
454                         }
455                 }
456
457                 // Is it a repeated post?
458                 if ($repeat_of != "") {
459                         $activityobjects = $xpath->query('activity:object', $entry)->item(0);
460
461                         if (is_object($activityobjects)) {
462
463                                 $orig_uri = $xpath->query("activity:object/atom:id", $activityobjects)->item(0)->nodeValue;
464                                 if (!isset($orig_uri))
465                                         $orig_uri = $xpath->query('atom:id/text()', $activityobjects)->item(0)->nodeValue;
466
467                                 $orig_links = $xpath->query("activity:object/atom:link[@rel='alternate']", $activityobjects);
468                                 if ($orig_links AND ($orig_links->length > 0))
469                                         foreach($orig_links->item(0)->attributes AS $attributes)
470                                                 if ($attributes->name == "href")
471                                                         $orig_link = $attributes->textContent;
472
473                                 if (!isset($orig_link))
474                                         $orig_link = $xpath->query("atom:link[@rel='alternate']", $activityobjects)->item(0)->nodeValue;
475
476                                 if (!isset($orig_link))
477                                         $orig_link =  ostatus_convert_href($orig_uri);
478
479                                 $orig_body = $xpath->query('activity:object/atom:content/text()', $activityobjects)->item(0)->nodeValue;
480                                 if (!isset($orig_body))
481                                         $orig_body = $xpath->query('atom:content/text()', $activityobjects)->item(0)->nodeValue;
482
483                                 $orig_created = $xpath->query('atom:published/text()', $activityobjects)->item(0)->nodeValue;
484
485                                 $orig_contact = $contact;
486                                 $orig_author = ostatus_fetchauthor($xpath, $activityobjects, $importer, $orig_contact, false);
487
488                                 //if (!intval(get_config('system','wall-to-wall_share'))) {
489                                 //      $prefix = share_header($orig_author['author-name'], $orig_author['author-link'], $orig_author['author-avatar'], "", $orig_created, $orig_link);
490                                 //      $item["body"] = $prefix.add_page_info_to_body(html2bbcode($orig_body))."[/share]";
491                                 //} else {
492                                         $item["author-name"] = $orig_author["author-name"];
493                                         $item["author-link"] = $orig_author["author-link"];
494                                         $item["author-avatar"] = $orig_author["author-avatar"];
495                                         $item["body"] = add_page_info_to_body(html2bbcode($orig_body));
496                                         $item["created"] = $orig_created;
497
498                                         $item["uri"] = $orig_uri;
499                                         $item["plink"] = $orig_link;
500                                 //}
501
502                                 $item["verb"] = $xpath->query('activity:verb/text()', $activityobjects)->item(0)->nodeValue;
503
504                                 $item["object-type"] = $xpath->query('activity:object/activity:object-type/text()', $activityobjects)->item(0)->nodeValue;
505                                 if (!isset($item["object-type"]))
506                                         $item["object-type"] = $xpath->query('activity:object-type/text()', $activityobjects)->item(0)->nodeValue;
507                         }
508                 }
509
510                 //if ($enclosure != "")
511                 //      $item["body"] .= add_page_info($enclosure);
512
513                 if (isset($item["parent-uri"])) {
514                         $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'",
515                                 intval($importer["uid"]), dbesc($item["parent-uri"]));
516
517                         if (!$r AND ($related != "")) {
518                                 $reply_path = str_replace("/notice/", "/api/statuses/show/", $related).".atom";
519
520                                 if ($reply_path != $related) {
521                                         logger("Fetching related items for user ".$importer["uid"]." from ".$reply_path, LOGGER_DEBUG);
522                                         $reply_xml = fetch_url($reply_path);
523
524                                         $reply_contact = $contact;
525                                         ostatus_import($reply_xml,$importer,$reply_contact, $reply_hub);
526
527                                         // After the import try to fetch the parent item again
528                                         $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'",
529                                                 intval($importer["uid"]), dbesc($item["parent-uri"]));
530                                 }
531                         }
532                         if ($r) {
533                                 $item["type"] = 'remote-comment';
534                                 $item["gravity"] = GRAVITY_COMMENT;
535                         }
536                 } else
537                         $item["parent-uri"] = $item["uri"];
538
539                 $item_id = ostatus_completion($conversation, $importer["uid"], $item);
540
541                 if (!$item_id) {
542                         logger("Error storing item", LOGGER_DEBUG);
543                         continue;
544                 }
545
546                 logger("Item was stored with id ".$item_id, LOGGER_DEBUG);
547                 $item["id"] = $item_id;
548
549                 if ($mention) {
550                         $u = q("SELECT `notify-flags`, `language`, `username`, `email` FROM user WHERE uid = %d LIMIT 1", intval($item['uid']));
551                         $r = q("SELECT `parent` FROM `item` WHERE `id` = %d", intval($item_id));
552
553                         notification(array(
554                                 'type'         => NOTIFY_TAGSELF,
555                                 'notify_flags' => $u[0]["notify-flags"],
556                                 'language'     => $u[0]["language"],
557                                 'to_name'      => $u[0]["username"],
558                                 'to_email'     => $u[0]["email"],
559                                 'uid'          => $item["uid"],
560                                 'item'         => $item,
561                                 'link'         => $a->get_baseurl().'/display/'.urlencode(get_item_guid($item_id)),
562                                 'source_name'  => $item["author-name"],
563                                 'source_link'  => $item["author-link"],
564                                 'source_photo' => $item["author-avatar"],
565                                 'verb'         => ACTIVITY_TAG,
566                                 'otype'        => 'item',
567                                 'parent'       => $r[0]["parent"]
568                         ));
569                 }
570         }
571 }
572
573 function ostatus_convert_href($href) {
574         $elements = explode(":",$href);
575
576         if ((count($elements) <= 2) OR ($elements[0] != "tag"))
577                 return $href;
578
579         $server = explode(",", $elements[1]);
580         $conversation = explode("=", $elements[2]);
581
582         if ((count($elements) == 4) AND ($elements[2] == "post"))
583                 return "http://".$server[0]."/notice/".$elements[3];
584
585         if ((count($conversation) != 2) OR ($conversation[1] ==""))
586                 return $href;
587
588         if ($elements[3] == "objectType=thread")
589                 return "http://".$server[0]."/conversation/".$conversation[1];
590         else
591                 return "http://".$server[0]."/notice/".$conversation[1];
592
593         return $href;
594 }
595
596 function check_conversations($mentions = false, $override = false) {
597         $last = get_config('system','ostatus_last_poll');
598
599         $poll_interval = intval(get_config('system','ostatus_poll_interval'));
600         if(! $poll_interval)
601                 $poll_interval = OSTATUS_DEFAULT_POLL_INTERVAL;
602
603         // Don't poll if the interval is set negative
604         if (($poll_interval < 0) AND !$override)
605                 return;
606
607         if (!$mentions) {
608                 $poll_timeframe = intval(get_config('system','ostatus_poll_timeframe'));
609                 if (!$poll_timeframe)
610                         $poll_timeframe = OSTATUS_DEFAULT_POLL_TIMEFRAME;
611         } else {
612                 $poll_timeframe = intval(get_config('system','ostatus_poll_timeframe'));
613                 if (!$poll_timeframe)
614                         $poll_timeframe = OSTATUS_DEFAULT_POLL_TIMEFRAME_MENTIONS;
615         }
616
617
618         if ($last AND !$override) {
619                 $next = $last + ($poll_interval * 60);
620                 if ($next > time()) {
621                         logger('poll interval not reached');
622                         return;
623                 }
624         }
625
626         logger('cron_start');
627
628         $start = date("Y-m-d H:i:s", time() - ($poll_timeframe * 60));
629
630         if ($mentions)
631                 $conversations = q("SELECT `term`.`oid`, `term`.`url`, `term`.`uid` FROM `term`
632                                         STRAIGHT_JOIN `thread` ON `thread`.`iid` = `term`.`oid` AND `thread`.`uid` = `term`.`uid`
633                                         WHERE `term`.`type` = 7 AND `term`.`term` > '%s' AND `thread`.`mention`
634                                         GROUP BY `term`.`url`, `term`.`uid` ORDER BY `term`.`term` DESC", dbesc($start));
635         else
636                 $conversations = q("SELECT `oid`, `url`, `uid` FROM `term`
637                                         WHERE `type` = 7 AND `term` > '%s'
638                                         GROUP BY `url`, `uid` ORDER BY `term` DESC", dbesc($start));
639
640         foreach ($conversations AS $conversation) {
641                 ostatus_completion($conversation['url'], $conversation['uid']);
642         }
643
644         logger('cron_end');
645
646         set_config('system','ostatus_last_poll', time());
647 }
648
649 function ostatus_completion($conversation_url, $uid, $item = array()) {
650
651         $a = get_app();
652
653         $item_stored = -1;
654
655         $conversation_url = ostatus_convert_href($conversation_url);
656
657         // If the thread shouldn't be completed then store the item and go away
658         if ((intval(get_config('system','ostatus_poll_interval')) == -2) AND (count($item) > 0)) {
659                 //$arr["app"] .= " (OStatus-NoCompletion)";
660                 $item_stored = item_store($item, true);
661                 return($item_stored);
662         }
663
664         // Get the parent
665         $parents = q("SELECT `id`, `parent`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `id` IN
666                         (SELECT `parent` FROM `item` WHERE `id` IN
667                                 (SELECT `oid` FROM `term` WHERE `uid` = %d AND `otype` = %d AND `type` = %d AND `url` = '%s'))",
668                         intval($uid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION), dbesc($conversation_url));
669
670         if ($parents)
671                 $parent = $parents[0];
672         elseif (count($item) > 0) {
673                 $parent = $item;
674                 $parent["type"] = "remote";
675                 $parent["verb"] = ACTIVITY_POST;
676                 $parent["visible"] = 1;
677         } else {
678                 // Preset the parent
679                 $r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid`=%d", $uid);
680                 if (!$r)
681                         return(-2);
682
683                 $parent = array();
684                 $parent["id"] = 0;
685                 $parent["parent"] = 0;
686                 $parent["uri"] = "";
687                 $parent["contact-id"] = $r[0]["id"];
688                 $parent["type"] = "remote";
689                 $parent["verb"] = ACTIVITY_POST;
690                 $parent["visible"] = 1;
691         }
692
693         $conv = str_replace("/conversation/", "/api/statusnet/conversation/", $conversation_url).".as";
694         $pageno = 1;
695         $items = array();
696
697         logger('fetching conversation url '.$conv.' for user '.$uid);
698
699         do {
700                 $conv_arr = z_fetch_url($conv."?page=".$pageno);
701
702                 // If it is a non-ssl site and there is an error, then try ssl or vice versa
703                 if (!$conv_arr["success"] AND (substr($conv, 0, 7) == "http://")) {
704                         $conv = str_replace("http://", "https://", $conv);
705                         $conv_as = fetch_url($conv."?page=".$pageno);
706                 } elseif (!$conv_arr["success"] AND (substr($conv, 0, 8) == "https://")) {
707                         $conv = str_replace("https://", "http://", $conv);
708                         $conv_as = fetch_url($conv."?page=".$pageno);
709                 } else
710                         $conv_as = $conv_arr["body"];
711
712                 $conv_as = str_replace(',"statusnet:notice_info":', ',"statusnet_notice_info":', $conv_as);
713                 $conv_as = json_decode($conv_as);
714
715                 $no_of_items = sizeof($items);
716
717                 if (@is_array($conv_as->items))
718                         foreach ($conv_as->items AS $single_item)
719                                 $items[$single_item->id] = $single_item;
720
721                 if ($no_of_items == sizeof($items))
722                         break;
723
724                 $pageno++;
725
726         } while (true);
727
728         logger('fetching conversation done. Found '.count($items).' items');
729
730         if (!sizeof($items)) {
731                 if (count($item) > 0) {
732                         //$arr["app"] .= " (OStatus-NoConvFetched)";
733                         $item_stored = item_store($item, true);
734
735                         if ($item_stored) {
736                                 logger("Conversation ".$conversation_url." couldn't be fetched. Item uri ".$item["uri"]." stored: ".$item_stored, LOGGER_DEBUG);
737                                 ostatus_store_conversation($item_id, $conversation_url);
738                         }
739
740                         return($item_stored);
741                 } else
742                         return(-3);
743         }
744
745         $items = array_reverse($items);
746
747         $r = q("SELECT `nurl` FROM `contact` WHERE `uid` = %d AND `self`", intval($uid));
748         $importer = $r[0];
749
750         foreach ($items as $single_conv) {
751
752                 // Test - remove before flight
753                 //$tempfile = tempnam(get_temppath(), "conversation");
754                 //file_put_contents($tempfile, json_encode($single_conv));
755
756                 $mention = false;
757
758                 if (isset($single_conv->object->id))
759                         $single_conv->id = $single_conv->object->id;
760
761                 $plink = ostatus_convert_href($single_conv->id);
762                 if (isset($single_conv->object->url))
763                         $plink = ostatus_convert_href($single_conv->object->url);
764
765                 if (@!$single_conv->id)
766                         continue;
767
768                 logger("Got id ".$single_conv->id, LOGGER_DEBUG);
769
770                 if ($first_id == "") {
771                         $first_id = $single_conv->id;
772
773                         // The first post of the conversation isn't our first post. There are three options:
774                         // 1. Our conversation hasn't the "real" thread starter
775                         // 2. This first post is a post inside our thread
776                         // 3. This first post is a post inside another thread
777                         if (($first_id != $parent["uri"]) AND ($parent["uri"] != "")) {
778                                 $new_parents = q("SELECT `id`, `parent`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `id` IN
779                                                         (SELECT `parent` FROM `item`
780                                                                 WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s')) LIMIT 1",
781                                         intval($uid), dbesc($first_id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN));
782                                 if ($new_parents) {
783                                         if ($new_parents[0]["parent"] == $parent["parent"]) {
784                                                 // Option 2: This post is already present inside our thread - but not as thread starter
785                                                 logger("Option 2: uri present in our thread: ".$first_id, LOGGER_DEBUG);
786                                                 $first_id = $parent["uri"];
787                                         } else {
788                                                 // Option 3: Not so good. We have mixed parents. We have to see how to clean this up.
789                                                 // For now just take the new parent.
790                                                 $parent = $new_parents[0];
791                                                 $first_id = $parent["uri"];
792                                                 logger("Option 3: mixed parents for uri ".$first_id, LOGGER_DEBUG);
793                                         }
794                                 } else {
795                                         // Option 1: We hadn't got the real thread starter
796                                         // We have to clean up our existing messages.
797                                         $parent["id"] = 0;
798                                         $parent["uri"] = $first_id;
799                                         logger("Option 1: we have a new parent: ".$first_id, LOGGER_DEBUG);
800                                 }
801                         } elseif ($parent["uri"] == "") {
802                                 $parent["id"] = 0;
803                                 $parent["uri"] = $first_id;
804                         }
805                 }
806
807                 $parent_uri = $parent["uri"];
808
809                 // "context" only seems to exist on older servers
810                 if (isset($single_conv->context->inReplyTo->id)) {
811                         $parent_exists = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1",
812                                                 intval($uid), dbesc($single_conv->context->inReplyTo->id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN));
813                         if ($parent_exists)
814                                 $parent_uri = $single_conv->context->inReplyTo->id;
815                 }
816
817                 // This is the current way
818                 if (isset($single_conv->object->inReplyTo->id)) {
819                         $parent_exists = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1",
820                                                 intval($uid), dbesc($single_conv->object->inReplyTo->id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN));
821                         if ($parent_exists)
822                                 $parent_uri = $single_conv->object->inReplyTo->id;
823                 }
824
825                 $message_exists = q("SELECT `id`, `parent`, `uri` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1",
826                                                 intval($uid), dbesc($single_conv->id),
827                                                 dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN));
828                 if ($message_exists) {
829                         logger("Message ".$single_conv->id." already existed on the system", LOGGER_DEBUG);
830
831                         if ($parent["id"] != 0) {
832                                 $existing_message = $message_exists[0];
833
834                                 // We improved the way we fetch OStatus messages, this shouldn't happen very often now
835                                 // To-Do: we have to change the shadow copies as well. This way here is really ugly.
836                                 if ($existing_message["parent"] != $parent["id"]) {
837                                         logger('updating id '.$existing_message["id"].' with parent '.$existing_message["parent"].' to parent '.$parent["id"].' uri '.$parent["uri"].' thread '.$parent_uri, LOGGER_DEBUG);
838
839                                         // Update the parent id of the selected item
840                                         $r = q("UPDATE `item` SET `parent` = %d, `parent-uri` = '%s' WHERE `id` = %d",
841                                                 intval($parent["id"]), dbesc($parent["uri"]), intval($existing_message["id"]));
842
843                                         // Update the parent uri in the thread - but only if it points to itself
844                                         $r = q("UPDATE `item` SET `thr-parent` = '%s' WHERE `id` = %d AND `uri` = `thr-parent`",
845                                                 dbesc($parent_uri), intval($existing_message["id"]));
846
847                                         // try to change all items of the same parent
848                                         $r = q("UPDATE `item` SET `parent` = %d, `parent-uri` = '%s' WHERE `parent` = %d",
849                                                 intval($parent["id"]), dbesc($parent["uri"]), intval($existing_message["parent"]));
850
851                                         // Update the parent uri in the thread - but only if it points to itself
852                                         $r = q("UPDATE `item` SET `thr-parent` = '%s' WHERE (`parent` = %d) AND (`uri` = `thr-parent`)",
853                                                 dbesc($parent["uri"]), intval($existing_message["parent"]));
854
855                                         // Now delete the thread
856                                         delete_thread($existing_message["parent"]);
857                                 }
858                         }
859
860                         // The item we are having on the system is the one that we wanted to store via the item array
861                         if (isset($item["uri"]) AND ($item["uri"] == $existing_message["uri"])) {
862                                 $item = array();
863                                 $item_stored = 0;
864                         }
865
866                         continue;
867                 }
868
869                 if (is_array($single_conv->to))
870                         foreach($single_conv->to AS $to)
871                                 if ($importer["nurl"] == normalise_link($to->id))
872                                         $mention = true;
873
874                 $actor = $single_conv->actor->id;
875                 if (isset($single_conv->actor->url))
876                         $actor = $single_conv->actor->url;
877
878                 $contact = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` != '%s'",
879                                 $uid, normalise_link($actor), NETWORK_STATUSNET);
880
881                 if (count($contact)) {
882                         logger("Found contact for url ".$actor, LOGGER_DEBUG);
883                         $contact_id = $contact[0]["id"];
884                 } else {
885                         logger("No contact found for url ".$actor, LOGGER_DEBUG);
886
887                         // Adding a global contact
888                         // To-Do: Use this data for the post
889                         $global_contact_id = get_contact($actor, 0);
890
891                         logger("Global contact ".$global_contact_id." found for url ".$actor, LOGGER_DEBUG);
892
893                         $contact_id = $parent["contact-id"];
894                 }
895
896                 $arr = array();
897                 $arr["network"] = NETWORK_OSTATUS;
898                 $arr["uri"] = $single_conv->id;
899                 $arr["plink"] = $plink;
900                 $arr["uid"] = $uid;
901                 $arr["contact-id"] = $contact_id;
902                 $arr["parent-uri"] = $parent_uri;
903                 $arr["created"] = $single_conv->published;
904                 $arr["edited"] = $single_conv->published;
905                 $arr["owner-name"] = $single_conv->actor->displayName;
906                 if ($arr["owner-name"] == '')
907                         $arr["owner-name"] = $single_conv->actor->contact->displayName;
908                 if ($arr["owner-name"] == '')
909                         $arr["owner-name"] = $single_conv->actor->portablecontacts_net->displayName;
910
911                 $arr["owner-link"] = $actor;
912                 $arr["owner-avatar"] = $single_conv->actor->image->url;
913                 $arr["author-name"] = $arr["owner-name"];
914                 $arr["author-link"] = $actor;
915                 $arr["author-avatar"] = $single_conv->actor->image->url;
916                 $arr["body"] = add_page_info_to_body(html2bbcode($single_conv->content));
917
918                 if (isset($single_conv->status_net->notice_info->source))
919                         $arr["app"] = strip_tags($single_conv->status_net->notice_info->source);
920                 elseif (isset($single_conv->statusnet->notice_info->source))
921                         $arr["app"] = strip_tags($single_conv->statusnet->notice_info->source);
922                 elseif (isset($single_conv->statusnet_notice_info->source))
923                         $arr["app"] = strip_tags($single_conv->statusnet_notice_info->source);
924                 elseif (isset($single_conv->provider->displayName))
925                         $arr["app"] = $single_conv->provider->displayName;
926                 else
927                         $arr["app"] = "OStatus";
928
929                 //$arr["app"] .= " (Conversation)";
930
931                 $arr["object"] = json_encode($single_conv);
932                 $arr["verb"] = $parent["verb"];
933                 $arr["visible"] = $parent["visible"];
934                 $arr["location"] = $single_conv->location->displayName;
935                 $arr["coord"] = trim($single_conv->location->lat." ".$single_conv->location->lon);
936
937                 // Is it a reshared item?
938                 if (isset($single_conv->verb) AND ($single_conv->verb == "share") AND isset($single_conv->object)) {
939                         if (is_array($single_conv->object))
940                                 $single_conv->object = $single_conv->object[0];
941
942                         logger("Found reshared item ".$single_conv->object->id);
943
944                         // $single_conv->object->context->conversation;
945
946                         if (isset($single_conv->object->object->id))
947                                 $arr["uri"] = $single_conv->object->object->id;
948                         else
949                                 $arr["uri"] = $single_conv->object->id;
950
951                         if (isset($single_conv->object->object->url))
952                                 $plink = ostatus_convert_href($single_conv->object->object->url);
953                         else
954                                 $plink = ostatus_convert_href($single_conv->object->url);
955
956                         if (isset($single_conv->object->object->content))
957                                 $arr["body"] = add_page_info_to_body(html2bbcode($single_conv->object->object->content));
958                         else
959                                 $arr["body"] = add_page_info_to_body(html2bbcode($single_conv->object->content));
960
961                         $arr["plink"] = $plink;
962
963                         $arr["created"] = $single_conv->object->published;
964                         $arr["edited"] = $single_conv->object->published;
965
966                         $arr["author-name"] = $single_conv->object->actor->displayName;
967                         if ($arr["owner-name"] == '')
968                                 $arr["author-name"] = $single_conv->object->actor->contact->displayName;
969
970                         $arr["author-link"] = $single_conv->object->actor->url;
971                         $arr["author-avatar"] = $single_conv->object->actor->image->url;
972
973                         $arr["app"] = $single_conv->object->provider->displayName."#";
974                         //$arr["verb"] = $single_conv->object->verb;
975
976                         $arr["location"] = $single_conv->object->location->displayName;
977                         $arr["coord"] = trim($single_conv->object->location->lat." ".$single_conv->object->location->lon);
978                 }
979
980                 if ($arr["location"] == "")
981                         unset($arr["location"]);
982
983                 if ($arr["coord"] == "")
984                         unset($arr["coord"]);
985
986                 // Copy fields from given item array
987                 if (isset($item["uri"]) AND (($item["uri"] == $arr["uri"]) OR ($item["uri"] ==  $single_conv->id))) {
988                         $copy_fields = array("owner-name", "owner-link", "owner-avatar", "author-name", "author-link", "author-avatar",
989                                                 "gravity", "body", "object-type", "object", "verb", "created", "edited", "coord", "tag",
990                                                 "title", "attach", "app", "type", "location", "contact-id", "uri");
991                         foreach ($copy_fields AS $field)
992                                 if (isset($item[$field]))
993                                         $arr[$field] = $item[$field];
994
995                         //$arr["app"] .= " (OStatus)";
996                 }
997
998                 $newitem = item_store($arr);
999                 if (!$newitem) {
1000                         logger("Item wasn't stored ".print_r($arr, true), LOGGER_DEBUG);
1001                         continue;
1002                 }
1003
1004                 if (isset($item["uri"]) AND ($item["uri"] == $arr["uri"])) {
1005                         $item = array();
1006                         $item_stored = $newitem;
1007                 }
1008
1009                 logger('Stored new item '.$plink.' for parent '.$arr["parent-uri"].' under id '.$newitem, LOGGER_DEBUG);
1010
1011                 // Add the conversation entry (but don't fetch the whole conversation)
1012                 ostatus_store_conversation($newitem, $conversation_url);
1013
1014                 if ($mention) {
1015                         $u = q("SELECT `notify-flags`, `language`, `username`, `email` FROM user WHERE uid = %d LIMIT 1", intval($uid));
1016                         $r = q("SELECT `parent` FROM `item` WHERE `id` = %d", intval($newitem));
1017
1018                         notification(array(
1019                                 'type'         => NOTIFY_TAGSELF,
1020                                 'notify_flags' => $u[0]["notify-flags"],
1021                                 'language'     => $u[0]["language"],
1022                                 'to_name'      => $u[0]["username"],
1023                                 'to_email'     => $u[0]["email"],
1024                                 'uid'          => $uid,
1025                                 'item'         => $arr,
1026                                 'link'         => $a->get_baseurl().'/display/'.urlencode(get_item_guid($newitem)),
1027                                 'source_name'  => $arr["author-name"],
1028                                 'source_link'  => $arr["author-link"],
1029                                 'source_photo' => $arr["author-avatar"],
1030                                 'verb'         => ACTIVITY_TAG,
1031                                 'otype'        => 'item',
1032                                 'parent'       => $r[0]["parent"]
1033                         ));
1034                 }
1035
1036                 // If the newly created item is the top item then change the parent settings of the thread
1037                 // This shouldn't happen anymore. This is supposed to be absolote.
1038                 if ($arr["uri"] == $first_id) {
1039                         logger('setting new parent to id '.$newitem);
1040                         $new_parents = q("SELECT `id`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `uid` = %d AND `id` = %d LIMIT 1",
1041                                 intval($uid), intval($newitem));
1042                         if ($new_parents)
1043                                 $parent = $new_parents[0];
1044                 }
1045         }
1046
1047         if (($item_stored < 0) AND (count($item) > 0)) {
1048                 //$arr["app"] .= " (OStatus-NoConvFound)";
1049                 $item_stored = item_store($item, true);
1050                 if ($item_stored) {
1051                         logger("Uri ".$item["uri"]." wasn't found in conversation ".$conversation_url, LOGGER_DEBUG);
1052                         ostatus_store_conversation($item_stored, $conversation_url);
1053                 }
1054         }
1055
1056         return($item_stored);
1057 }
1058
1059 function ostatus_store_conversation($itemid, $conversation_url) {
1060         global $a;
1061
1062         $conversation_url = ostatus_convert_href($conversation_url);
1063
1064         $messages = q("SELECT `uid`, `parent`, `created`, `received`, `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($itemid));
1065         if (!$messages)
1066                 return;
1067         $message = $messages[0];
1068
1069         // Store conversation url if not done before
1070         $conversation = q("SELECT `url` FROM `term` WHERE `uid` = %d AND `oid` = %d AND `otype` = %d AND `type` = %d",
1071                 intval($message["uid"]), intval($itemid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION));
1072
1073         if (!$conversation) {
1074                 $r = q("INSERT INTO `term` (`uid`, `oid`, `otype`, `type`, `term`, `url`, `created`, `received`, `guid`) VALUES (%d, %d, %d, %d, '%s', '%s', '%s', '%s', '%s')",
1075                         intval($message["uid"]), intval($itemid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION),
1076                         dbesc($message["created"]), dbesc($conversation_url), dbesc($message["created"]), dbesc($message["received"]), dbesc($message["guid"]));
1077                 logger('Storing conversation url '.$conversation_url.' for id '.$itemid);
1078         }
1079 }
1080
1081 function xml_add_element($doc, $parent, $element, $value = "", $attributes = array()) {
1082         $element = $doc->createElement($element, xmlify($value));
1083
1084         foreach ($attributes AS $key => $value) {
1085                 $attribute = $doc->createAttribute($key);
1086                 $attribute->value = xmlify($value);
1087                 $element->appendChild($attribute);
1088         }
1089
1090         $parent->appendChild($element);
1091 }
1092
1093 function ostatus_format_picture_post($body) {
1094         $siteinfo = get_attached_data($body);
1095
1096         if (($siteinfo["type"] == "photo")) {
1097                 if (isset($siteinfo["preview"]))
1098                         $preview = $siteinfo["preview"];
1099                 else
1100                         $preview = $siteinfo["image"];
1101
1102                 // Is it a remote picture? Then make a smaller preview here
1103                 $preview = proxy_url($preview, false, PROXY_SIZE_SMALL);
1104
1105                 // Is it a local picture? Then make it smaller here
1106                 $preview = str_replace(array("-0.jpg", "-0.png"), array("-2.jpg", "-2.png"), $preview);
1107                 $preview = str_replace(array("-1.jpg", "-1.png"), array("-2.jpg", "-2.png"), $preview);
1108
1109                 if (isset($siteinfo["url"]))
1110                         $url = $siteinfo["url"];
1111                 else
1112                         $url = $siteinfo["image"];
1113
1114                 $body = trim($siteinfo["text"])." [url]".$url."[/url]\n[img]".$preview."[/img]";
1115         }
1116
1117         return $body;
1118 }
1119
1120 function ostatus_add_header($doc, $owner) {
1121         $a = get_app();
1122
1123         $r = q("SELECT * FROM `profile` WHERE `uid` = %d AND `is-default`",
1124                 intval($owner["uid"]));
1125         if (!$r)
1126                 return;
1127
1128         $profile = $r[0];
1129
1130         $root = $doc->createElementNS(NS_ATOM, 'feed');
1131         $doc->appendChild($root);
1132
1133         $root->setAttribute("xmlns:thr", NS_THR);
1134         $root->setAttribute("xmlns:georss", NS_GEORSS);
1135         $root->setAttribute("xmlns:activity", NS_ACTIVITY);
1136         $root->setAttribute("xmlns:media", NS_MEDIA);
1137         $root->setAttribute("xmlns:poco", NS_POCO);
1138         $root->setAttribute("xmlns:ostatus", NS_OSTATUS);
1139         $root->setAttribute("xmlns:statusnet", NS_STATUSNET);
1140
1141         $attributes = array("uri" => "https://friendi.ca", "version" => FRIENDICA_VERSION."-".DB_UPDATE_VERSION);
1142         xml_add_element($doc, $root, "generator", FRIENDICA_PLATFORM, $attributes);
1143         xml_add_element($doc, $root, "id", $a->get_baseurl()."/profile/".$owner["nick"]);
1144         xml_add_element($doc, $root, "title", sprintf("%s timeline", $profile["name"]));
1145         xml_add_element($doc, $root, "subtitle", sprintf("Updates from %s on %s", $profile["name"], $a->config["sitename"]));
1146         xml_add_element($doc, $root, "logo", $profile["photo"]);
1147         xml_add_element($doc, $root, "updated", datetime_convert("UTC", "UTC", "now", ATOM_TIME));
1148
1149         $author = ostatus_add_author($doc, $owner, $profile);
1150         $root->appendChild($author);
1151
1152         $attributes = array("href" => $owner["url"], "rel" => "alternate", "type" => "text/html");
1153         xml_add_element($doc, $root, "link", "", $attributes);
1154
1155         // To-Do: We have to find out what this is
1156         //$attributes = array("href" => $a->get_baseurl()."/sup",
1157         //              "rel" => "http://api.friendfeed.com/2008/03#sup",
1158         //              "type" => "application/json");
1159         //xml_add_element($doc, $root, "link", "", $attributes);
1160
1161         ostatus_hublinks($doc, $root);
1162
1163         $attributes = array("href" => $a->get_baseurl()."/salmon/".$owner["nick"], "rel" => "salmon");
1164         xml_add_element($doc, $root, "link", "", $attributes);
1165
1166         $attributes = array("href" => $a->get_baseurl()."/salmon/".$owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-replies");
1167         xml_add_element($doc, $root, "link", "", $attributes);
1168
1169         $attributes = array("href" => $a->get_baseurl()."/salmon/".$owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-mention");
1170         xml_add_element($doc, $root, "link", "", $attributes);
1171
1172         $attributes = array("href" => $a->get_baseurl()."/api/statuses/user_timeline/".$owner["nick"].".atom",
1173                         "rel" => "self", "type" => "application/atom+xml");
1174         xml_add_element($doc, $root, "link", "", $attributes);
1175
1176         return $root;
1177 }
1178
1179 function ostatus_hublinks($doc, $root) {
1180         $a = get_app();
1181         $hub = get_config('system','huburl');
1182
1183         $hubxml = '';
1184         if(strlen($hub)) {
1185                 $hubs = explode(',', $hub);
1186                 if(count($hubs)) {
1187                         foreach($hubs as $h) {
1188                                 $h = trim($h);
1189                                 if(! strlen($h))
1190                                         continue;
1191                                 if ($h === '[internal]')
1192                                         $h = $a->get_baseurl() . '/pubsubhubbub';
1193                                 xml_add_element($doc, $root, "link", "", array("href" => $h, "rel" => "hub"));
1194                         }
1195                 }
1196         }
1197 }
1198
1199 function ostatus_get_attachment($doc, $root, $item) {
1200         $o = "";
1201         $siteinfo = get_attached_data($item["body"]);
1202
1203         switch($siteinfo["type"]) {
1204                 case 'link':
1205                         $attributes = array("rel" => "enclosure",
1206                                         "href" => $siteinfo["url"],
1207                                         "type" => "text/html; charset=UTF-8",
1208                                         "length" => "",
1209                                         "title" => $siteinfo["title"]);
1210                         xml_add_element($doc, $root, "link", "", $attributes);
1211                         break;
1212                 case 'photo':
1213                         $imgdata = get_photo_info($siteinfo["image"]);
1214                         $attributes = array("rel" => "enclosure",
1215                                         "href" => $siteinfo["image"],
1216                                         "type" => $imgdata["mime"],
1217                                         "length" => intval($imgdata["size"]));
1218                         xml_add_element($doc, $root, "link", "", $attributes);
1219                         break;
1220                 case 'video':
1221                         $attributes = array("rel" => "enclosure",
1222                                         "href" => $siteinfo["url"],
1223                                         "type" => "text/html; charset=UTF-8",
1224                                         "length" => "",
1225                                         "title" => $siteinfo["title"]);
1226                         xml_add_element($doc, $root, "link", "", $attributes);
1227                         break;
1228                 default:
1229                         break;
1230         }
1231
1232         if (($siteinfo["type"] != "photo") AND isset($siteinfo["image"])) {
1233                 $photodata = get_photo_info($siteinfo["image"]);
1234
1235                 $attributes = array("rel" => "preview", "href" => $siteinfo["image"], "media:width" => $photodata[0], "media:height" => $photodata[1]);
1236                 xml_add_element($doc, $root, "link", "", $attributes);
1237         }
1238
1239
1240         $arr = explode('[/attach],',$item['attach']);
1241         if(count($arr)) {
1242                 foreach($arr as $r) {
1243                         $matches = false;
1244                         $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
1245                         if($cnt) {
1246                                 $attributes = array("rel" => "enclosure",
1247                                                 "href" => $matches[1],
1248                                                 "type" => $matches[3]);
1249
1250                                 if(intval($matches[2]))
1251                                         $attributes["length"] = intval($matches[2]);
1252
1253                                 if(trim($matches[4]) != "")
1254                                         $attributes["title"] = trim($matches[4]);
1255
1256                                 xml_add_element($doc, $root, "link", "", $attributes);
1257                         }
1258                 }
1259         }
1260 }
1261
1262 function ostatus_add_author($doc, $owner, $profile) {
1263         $a = get_app();
1264
1265         $author = $doc->createElement("author");
1266         xml_add_element($doc, $author, "activity:object-type", ACTIVITY_OBJ_PERSON);
1267         xml_add_element($doc, $author, "uri", $owner["url"]);
1268         xml_add_element($doc, $author, "name", $profile["name"]);
1269
1270         $attributes = array("rel" => "alternate", "type" => "text/html", "href" => $owner["url"]);
1271         xml_add_element($doc, $author, "link", "", $attributes);
1272
1273         $attributes = array(
1274                         "rel" => "avatar",
1275                         "type" => "image/jpeg", // To-Do?
1276                         "media:width" => 175,
1277                         "media:height" => 175,
1278                         "href" => $profile["photo"]);
1279         xml_add_element($doc, $author, "link", "", $attributes);
1280
1281         $attributes = array(
1282                         "rel" => "avatar",
1283                         "type" => "image/jpeg", // To-Do?
1284                         "media:width" => 80,
1285                         "media:height" => 80,
1286                         "href" => $profile["thumb"]);
1287         xml_add_element($doc, $author, "link", "", $attributes);
1288
1289         xml_add_element($doc, $author, "poco:preferredUsername", $owner["nick"]);
1290         xml_add_element($doc, $author, "poco:displayName", $profile["name"]);
1291         xml_add_element($doc, $author, "poco:note", $profile["about"]);
1292
1293         if (trim($owner["location"]) != "") {
1294                 $element = $doc->createElement("poco:address");
1295                 xml_add_element($doc, $element, "poco:formatted", $owner["location"]);
1296                 $author->appendChild($element);
1297         }
1298
1299         if (trim($profile["homepage"]) != "") {
1300                 $urls = $doc->createElement("poco:urls");
1301                 xml_add_element($doc, $urls, "poco:type", "homepage");
1302                 xml_add_element($doc, $urls, "poco:value", $profile["homepage"]);
1303                 xml_add_element($doc, $urls, "poco:primary", "true");
1304                 $author->appendChild($urls);
1305         }
1306
1307         xml_add_element($doc, $author, "followers", "", array("url" => $a->get_baseurl()."/viewcontacts/".$owner["nick"]));
1308         xml_add_element($doc, $author, "statusnet:profile_info", "", array("local_id" => $owner["uid"]));
1309
1310         return $author;
1311 }
1312
1313 /*
1314 To-Do: Picture attachments should look like this:
1315
1316 <a href="https://status.pirati.ca/attachment/572819" title="https://status.pirati.ca/file/heluecht-20151202T222602-rd3u49p.gif"
1317 class="attachment thumbnail" id="attachment-572819" rel="nofollow external">https://status.pirati.ca/attachment/572819</a>
1318
1319 */
1320
1321 function ostatus_entry($doc, $item, $owner, $toplevel = false) {
1322         $a = get_app();
1323
1324         if (!$toplevel) {
1325                 $entry = $doc->createElement("entry");
1326                 $title = sprintf("New note by %s", $owner["nick"]);
1327         } else {
1328                 $entry = $doc->createElementNS(NS_ATOM, "entry");
1329
1330                 $entry->setAttribute("xmlns:thr", NS_THR);
1331                 $entry->setAttribute("xmlns:georss", NS_GEORSS);
1332                 $entry->setAttribute("xmlns:activity", NS_ACTIVITY);
1333                 $entry->setAttribute("xmlns:media", NS_MEDIA);
1334                 $entry->setAttribute("xmlns:poco", NS_POCO);
1335                 $entry->setAttribute("xmlns:ostatus", NS_OSTATUS);
1336                 $entry->setAttribute("xmlns:statusnet", NS_STATUSNET);
1337
1338                 $r = q("SELECT * FROM `profile` WHERE `uid` = %d AND `is-default`",
1339                         intval($owner["uid"]));
1340                 if (!$r)
1341                         return;
1342
1343                 $profile = $r[0];
1344
1345                 $author = ostatus_add_author($doc, $owner, $profile);
1346                 $entry->appendChild($author);
1347
1348                 $title = sprintf("New comment by %s", $owner["nick"]);
1349         }
1350
1351         // To use the object-type "bookmark" we have to implement these elements:
1352         //
1353         // <activity:object-type>http://activitystrea.ms/schema/1.0/bookmark</activity:object-type>
1354         // <title>Historic Rocket Landing</title>
1355         // <summary>Nur ein Testbeitrag.</summary>
1356         // <link rel="related" href="https://www.youtube.com/watch?v=9pillaOxGCo"/>
1357         // <link rel="preview" href="https://pirati.cc/file/thumb-4526-450x338-b48c8055f0c2fed0c3f67adc234c4b99484a90c42ed3cac73dc1081a4d0a7bc1.jpg.jpg" media:width="450" media:height="338"/>
1358         //
1359         // But: it seems as if it doesn't federate well between the GS servers
1360         // So we just set it to "note" to be sure that it reaches their target systems
1361
1362         xml_add_element($doc, $entry, "activity:object-type", ACTIVITY_OBJ_NOTE);
1363         xml_add_element($doc, $entry, "id", $item["uri"]);
1364         xml_add_element($doc, $entry, "title", $title);
1365
1366         if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
1367                 $body = fix_private_photos($item['body'],$owner['uid'],$item, 0);
1368         else
1369                 $body = $item['body'];
1370
1371         $body = ostatus_format_picture_post($body);
1372
1373         if ($item['title'] != "")
1374                 $body = "[b]".$item['title']."[/b]\n\n".$body;
1375
1376         //$body = bb_remove_share_information($body);
1377         $body = bbcode($body, false, false, 7);
1378
1379         xml_add_element($doc, $entry, "content", $body, array("type" => "html"));
1380
1381         xml_add_element($doc, $entry, "link", "", array("rel" => "alternate", "type" => "text/html",
1382                                                         "href" => $a->get_baseurl()."/display/".$item["guid"]));
1383
1384         xml_add_element($doc, $entry, "status_net", "", array("notice_id" => $item["id"]));
1385         xml_add_element($doc, $entry, "activity:verb", construct_verb($item));
1386         xml_add_element($doc, $entry, "published", datetime_convert("UTC","UTC",$item["created"]."+00:00",ATOM_TIME));
1387         xml_add_element($doc, $entry, "updated", datetime_convert("UTC","UTC",$item["edited"]."+00:00",ATOM_TIME));
1388
1389         $mentioned = array();
1390
1391         if (($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
1392                 $parent = q("SELECT `guid` FROM `item` WHERE `id` = %d", intval($item["parent"]));
1393                 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
1394
1395                 $attributes = array(
1396                                 "ref" => $parent_item,
1397                                 "type" => "text/html",
1398                                 "href" => $a->get_baseurl()."/display/".$parent[0]["guid"]);
1399                 xml_add_element($doc, $entry, "thr:in-reply-to", "", $attributes);
1400
1401                 $attributes = array(
1402                                 "rel" => "related",
1403                                 "href" => $a->get_baseurl()."/display/".$parent[0]["guid"]);
1404                 xml_add_element($doc, $entry, "link", "", $attributes);
1405
1406                 $mentioned[$parent[0]["author-link"]] = $parent[0]["author-link"];
1407                 $mentioned[$parent[0]["owner-link"]] = $parent[0]["owner-link"];
1408
1409                 $thrparent = q("SELECT `guid`, `author-link`, `owner-link` FROM `item` WHERE `uid` = %d AND `uri` = '%s'",
1410                                 intval($owner["uid"]),
1411                                 dbesc($parent_item));
1412                 if ($thrparent) {
1413                         $mentioned[$thrparent[0]["author-link"]] = $thrparent[0]["author-link"];
1414                         $mentioned[$thrparent[0]["owner-link"]] = $thrparent[0]["owner-link"];
1415                 }
1416         }
1417
1418         xml_add_element($doc, $entry, "link", "", array("rel" => "ostatus:conversation",
1419                                                         "href" => $a->get_baseurl()."/display/".$owner["nick"]."/".$item["parent"]));
1420         xml_add_element($doc, $entry, "ostatus:conversation", $a->get_baseurl()."/display/".$owner["nick"]."/".$item["parent"]);
1421
1422         $tags = item_getfeedtags($item);
1423
1424         if(count($tags))
1425                 foreach($tags as $t)
1426                         if ($t[0] == "@")
1427                                 $mentioned[$t[1]] = $t[1];
1428
1429         // Make sure that mentions are accepted (GNU Social has problems with mixing HTTP and HTTPS)
1430         $newmentions = array();
1431         foreach ($mentioned AS $mention) {
1432                 $newmentions[str_replace("http://", "https://", $mention)] = str_replace("http://", "https://", $mention);
1433                 $newmentions[str_replace("https://", "http://", $mention)] = str_replace("https://", "http://", $mention);
1434         }
1435         $mentioned = $newmentions;
1436
1437         foreach ($mentioned AS $mention) {
1438                 $r = q("SELECT `forum`, `prv` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s'",
1439                         intval($owner["uid"]),
1440                         dbesc(normalise_link($mention)));
1441                 if ($r[0]["forum"] OR $r[0]["prv"])
1442                         xml_add_element($doc, $entry, "link", "", array("rel" => "mentioned",
1443                                                                                 "ostatus:object-type" => ACTIVITY_OBJ_GROUP,
1444                                                                                 "href" => $mention));
1445                 else
1446                         xml_add_element($doc, $entry, "link", "", array("rel" => "mentioned",
1447                                                                                 "ostatus:object-type" => ACTIVITY_OBJ_PERSON,
1448                                                                                 "href" => $mention));
1449         }
1450
1451         if (!$item["private"])
1452                 xml_add_element($doc, $entry, "link", "", array("rel" => "mentioned",
1453                                                                 "ostatus:object-type" => "http://activitystrea.ms/schema/1.0/collection",
1454                                                                 "href" => "http://activityschema.org/collection/public"));
1455
1456         if(count($tags))
1457                 foreach($tags as $t)
1458                         if ($t[0] != "@")
1459                                 xml_add_element($doc, $entry, "category", "", array("term" => $t[2]));
1460
1461         ostatus_get_attachment($doc, $entry, $item);
1462
1463         // To-Do:
1464         // The API call has yet to be implemented
1465         //$attributes = array("href" => $a->get_baseurl()."/api/statuses/show/".$item["id"].".atom",
1466         //              "rel" => "self", "type" => "application/atom+xml");
1467         //xml_add_element($doc, $entry, "link", "", $attributes);
1468
1469         //$attributes = array("href" => $a->get_baseurl()."/api/statuses/show/".$item["id"].".atom",
1470         //              "rel" => "edit", "type" => "application/atom+xml");
1471         //xml_add_element($doc, $entry, "link", "", $attributes);
1472
1473         $app = $item["app"];
1474         if ($app == "")
1475                 $app = "web";
1476
1477         xml_add_element($doc, $entry, "statusnet:notice_info", "", array("local_id" => $item["id"], "source" => $app));
1478
1479         return $entry;
1480 }
1481
1482 function ostatus_feed(&$a, $owner_nick, $last_update) {
1483
1484         $r = q("SELECT `contact`.*, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
1485                         FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
1486                         WHERE `contact`.`self` AND `user`.`nickname` = '%s' LIMIT 1",
1487                         dbesc($owner_nick));
1488         if (!$r)
1489                 return;
1490
1491         $owner = $r[0];
1492
1493         if(!strlen($last_update))
1494                 $last_update = 'now -30 days';
1495
1496         $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
1497
1498         $items = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id` FROM `item`
1499                         INNER JOIN `thread` ON `thread`.`iid` = `item`.`parent`
1500                         LEFT JOIN `item` AS `thritem` ON `thritem`.`uri`=`item`.`thr-parent` AND `thritem`.`uid`=`item`.`uid`
1501                         WHERE `item`.`uid` = %d AND `item`.`received` > '%s' AND NOT `item`.`private` AND NOT `item`.`deleted`
1502                                 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid`  = '' AND `item`.`deny_gid`  = ''
1503                                 AND ((`item`.`wall` AND (`item`.`parent` = `item`.`id`))
1504                                         OR (`item`.`network` = '%s' AND ((`thread`.`network`='%s') OR (`thritem`.`network` = '%s'))) AND `thread`.`mention`)
1505                                 AND (`item`.`owner-link` IN ('%s', '%s'))
1506                         ORDER BY `item`.`received` DESC
1507                         LIMIT 0, 300",
1508                         intval($owner["uid"]), dbesc($check_date),
1509                         dbesc(NETWORK_DFRN), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS),
1510                         dbesc($owner["nurl"]), dbesc(str_replace("http://", "https://", $owner["nurl"]))
1511                 );
1512
1513         $doc = new DOMDocument('1.0', 'utf-8');
1514         $doc->formatOutput = true;
1515
1516         $root = ostatus_add_header($doc, $owner);
1517
1518         foreach ($items AS $item) {
1519                 $entry = ostatus_entry($doc, $item, $owner);
1520                 $root->appendChild($entry);
1521         }
1522
1523         return(trim($doc->saveXML()));
1524 }
1525
1526 function ostatus_salmon($item,$owner) {
1527
1528         $doc = new DOMDocument('1.0', 'utf-8');
1529         $doc->formatOutput = true;
1530
1531         $entry = ostatus_entry($doc, $item, $owner, true);
1532
1533         $doc->appendChild($entry);
1534
1535         return(trim($doc->saveXML()));
1536 }
1537 ?>