]> git.mxchange.org Git - friendica.git/blob - include/items.php
Merge pull request #1636 from rabuzarus/albums_widget
[friendica.git] / include / items.php
1 <?php
2
3 require_once('include/bbcode.php');
4 require_once('include/oembed.php');
5 require_once('include/salmon.php');
6 require_once('include/crypto.php');
7 require_once('include/Photo.php');
8 require_once('include/tags.php');
9 require_once('include/files.php');
10 require_once('include/text.php');
11 require_once('include/email.php');
12 require_once('include/ostatus_conversation.php');
13 require_once('include/threads.php');
14 require_once('include/socgraph.php');
15 require_once('mod/share.php');
16
17 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0, $forpubsub = false) {
18
19
20         $sitefeed    = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
21         $public_feed = (($dfrn_id) ? false : true);
22         $starred     = false;   // not yet implemented, possible security issues
23         $converse    = false;
24
25         if($public_feed && $a->argc > 2) {
26                 for($x = 2; $x < $a->argc; $x++) {
27                         if($a->argv[$x] == 'converse')
28                                 $converse = true;
29                         if($a->argv[$x] == 'starred')
30                                 $starred = true;
31                         if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
32                                 $category = $a->argv[$x+1];
33                 }
34         }
35
36
37
38         // default permissions - anonymous user
39
40         $sql_extra = " AND `allow_cid` = '' AND `allow_gid` = '' AND `deny_cid`  = '' AND `deny_gid`  = '' ";
41
42         $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
43                 FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
44                 WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
45                 dbesc($owner_nick)
46         );
47
48         if(! count($r))
49                 killme();
50
51         $owner = $r[0];
52         $owner_id = $owner['user_uid'];
53         $owner_nick = $owner['nickname'];
54
55         $birthday = feed_birthday($owner_id,$owner['timezone']);
56
57         $sql_post_table = "";
58         $visibility = "";
59
60         if(! $public_feed) {
61
62                 $sql_extra = '';
63                 switch($direction) {
64                         case (-1):
65                                 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
66                                 $my_id = $dfrn_id;
67                                 break;
68                         case 0:
69                                 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
70                                 $my_id = '1:' . $dfrn_id;
71                                 break;
72                         case 1:
73                                 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
74                                 $my_id = '0:' . $dfrn_id;
75                                 break;
76                         default:
77                                 return false;
78                                 break; // NOTREACHED
79                 }
80
81                 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
82                         intval($owner_id)
83                 );
84
85                 if(! count($r))
86                         killme();
87
88                 $contact = $r[0];
89                 require_once('include/security.php');
90                 $groups = init_groups_visitor($contact['id']);
91
92                 if(count($groups)) {
93                         for($x = 0; $x < count($groups); $x ++)
94                                 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
95                         $gs = implode('|', $groups);
96                 }
97                 else
98                         $gs = '<<>>' ; // Impossible to match
99
100                 $sql_extra = sprintf("
101                         AND ( `allow_cid` = '' OR     `allow_cid` REGEXP '<%d>' )
102                         AND ( `deny_cid`  = '' OR NOT `deny_cid`  REGEXP '<%d>' )
103                         AND ( `allow_gid` = '' OR     `allow_gid` REGEXP '%s' )
104                         AND ( `deny_gid`  = '' OR NOT `deny_gid`  REGEXP '%s')
105                 ",
106                         intval($contact['id']),
107                         intval($contact['id']),
108                         dbesc($gs),
109                         dbesc($gs)
110                 );
111         }
112
113         if($public_feed)
114                 $sort = 'DESC';
115         else
116                 $sort = 'ASC';
117
118         // Include answers to status.net posts in pubsub feeds
119         if($forpubsub) {
120                 $sql_post_table = "INNER JOIN `thread` ON `thread`.`iid` = `item`.`parent` ";
121                 $visibility = "OR (`item`.`network` = 'dfrn' AND `thread`.`network`='stat')";
122                 $date_field = "`received`";
123                 $sql_order = "`item`.`received` DESC";
124         } else {
125                 $date_field = "`changed`";
126                 $sql_order = "`item`.`parent` ".$sort.", `item`.`created` ASC";
127         }
128
129         if(! strlen($last_update))
130                 $last_update = 'now -30 days';
131
132         if(isset($category)) {
133                 $sql_post_table = sprintf("INNER JOIN (SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d ORDER BY `tid` DESC) AS `term` ON `item`.`id` = `term`.`oid` ",
134                                 dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
135                 //$sql_extra .= file_tag_file_query('item',$category,'category');
136         }
137
138         if($public_feed) {
139                 if(! $converse)
140                         $sql_extra .= " AND `contact`.`self` = 1 ";
141         }
142
143         $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
144
145         //      AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
146         //      dbesc($check_date),
147
148         $r = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id`,
149                 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
150                 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
151                 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
152                 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
153                 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
154                 FROM `item` $sql_post_table
155                 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
156                 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
157                 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
158                 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
159                 AND ((`item`.`wall` = 1) $visibility) AND `item`.$date_field > '%s'
160                 $sql_extra
161                 ORDER BY $sql_order LIMIT 0, 300",
162                 intval($owner_id),
163                 dbesc($check_date),
164                 dbesc($sort)
165         );
166
167         // Will check further below if this actually returned results.
168         // We will provide an empty feed if that is the case.
169
170         $items = $r;
171
172         $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
173
174         $atom = '';
175
176         $hubxml = feed_hublinks();
177
178         $salmon = feed_salmonlinks($owner_nick);
179
180         $alternatelink = $owner['url'];
181
182         if(isset($category))
183                 $alternatelink .= "/category/".$category;
184
185         $atom .= replace_macros($feed_template, array(
186                 '$version'      => xmlify(FRIENDICA_VERSION),
187                 '$feed_id'      => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
188                 '$feed_title'   => xmlify($owner['name']),
189                 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
190                 '$hub'          => $hubxml,
191                 '$salmon'       => $salmon,
192                 '$alternatelink' => xmlify($alternatelink),
193                 '$name'         => xmlify($owner['name']),
194                 '$profile_page' => xmlify($owner['url']),
195                 '$photo'        => xmlify($owner['photo']),
196                 '$thumb'        => xmlify($owner['thumb']),
197                 '$picdate'      => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
198                 '$uridate'      => xmlify(datetime_convert('UTC','UTC',$owner['uri-date']    . '+00:00' , ATOM_TIME)) ,
199                 '$namdate'      => xmlify(datetime_convert('UTC','UTC',$owner['name-date']   . '+00:00' , ATOM_TIME)) ,
200                 '$birthday'     => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
201                 '$community'    => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
202         ));
203
204         call_hooks('atom_feed', $atom);
205
206         if(! count($items)) {
207
208                 call_hooks('atom_feed_end', $atom);
209
210                 $atom .= '</feed>' . "\r\n";
211                 return $atom;
212         }
213
214         foreach($items as $item) {
215
216                 // prevent private email from leaking.
217                 if($item['network'] === NETWORK_MAIL)
218                         continue;
219
220                 // public feeds get html, our own nodes use bbcode
221
222                 if($public_feed) {
223                         $type = 'html';
224                         // catch any email that's in a public conversation and make sure it doesn't leak
225                         if($item['private'])
226                                 continue;
227                 }
228                 else {
229                         $type = 'text';
230                 }
231
232                 $atom .= atom_entry($item,$type,null,$owner,true);
233         }
234
235         call_hooks('atom_feed_end', $atom);
236
237         $atom .= '</feed>' . "\r\n";
238
239         return $atom;
240 }
241
242
243 function construct_verb($item) {
244         if($item['verb'])
245                 return $item['verb'];
246         return ACTIVITY_POST;
247 }
248
249 function construct_activity_object($item) {
250
251         if($item['object']) {
252                 $o = '<as:object>' . "\r\n";
253                 $r = parse_xml_string($item['object'],false);
254
255
256                 if(! $r)
257                         return '';
258                 if($r->type)
259                         $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
260                 if($r->id)
261                         $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
262                 if($r->title)
263                         $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
264                 if($r->link) {
265                         if(substr($r->link,0,1) === '<') {
266                                 // patch up some facebook "like" activity objects that got stored incorrectly
267                                 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
268                                 // we can probably remove this hack here and in the following function in a few months time.
269                                 if(strstr($r->link,'&') && (! strstr($r->link,'&amp;')))
270                                         $r->link = str_replace('&','&amp;', $r->link);
271                                 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
272                                 $o .= $r->link;
273                         }
274                         else
275                                 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
276                 }
277                 if($r->content)
278                         $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
279                 $o .= '</as:object>' . "\r\n";
280                 return $o;
281         }
282
283         return '';
284 }
285
286 function construct_activity_target($item) {
287
288         if($item['target']) {
289                 $o = '<as:target>' . "\r\n";
290                 $r = parse_xml_string($item['target'],false);
291                 if(! $r)
292                         return '';
293                 if($r->type)
294                         $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
295                 if($r->id)
296                         $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
297                 if($r->title)
298                         $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
299                 if($r->link) {
300                         if(substr($r->link,0,1) === '<') {
301                                 if(strstr($r->link,'&') && (! strstr($r->link,'&amp;')))
302                                         $r->link = str_replace('&','&amp;', $r->link);
303                                 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
304                                 $o .= $r->link;
305                         }
306                         else
307                                 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
308                 }
309                 if($r->content)
310                         $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
311                 $o .= '</as:target>' . "\r\n";
312                 return $o;
313         }
314
315         return '';
316 }
317
318 /* limit_body_size()
319  *
320  *              The purpose of this function is to apply system message length limits to
321  *              imported messages without including any embedded photos in the length
322  */
323 if(! function_exists('limit_body_size')) {
324 function limit_body_size($body) {
325
326 //      logger('limit_body_size: start', LOGGER_DEBUG);
327
328         $maxlen = get_max_import_size();
329
330         // If the length of the body, including the embedded images, is smaller
331         // than the maximum, then don't waste time looking for the images
332         if($maxlen && (strlen($body) > $maxlen)) {
333
334                 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
335
336                 $orig_body = $body;
337                 $new_body = '';
338                 $textlen = 0;
339                 $max_found = false;
340
341                 $img_start = strpos($orig_body, '[img');
342                 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
343                 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
344                 while(($img_st_close !== false) && ($img_end !== false)) {
345
346                         $img_st_close++; // make it point to AFTER the closing bracket
347                         $img_end += $img_start;
348                         $img_end += strlen('[/img]');
349
350                         if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
351                                 // This is an embedded image
352
353                                 if( ($textlen + $img_start) > $maxlen ) {
354                                         if($textlen < $maxlen) {
355                                                 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
356                                                 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
357                                                 $textlen = $maxlen;
358                                         }
359                                 }
360                                 else {
361                                         $new_body = $new_body . substr($orig_body, 0, $img_start);
362                                         $textlen += $img_start;
363                                 }
364
365                                 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
366                         }
367                         else {
368
369                                 if( ($textlen + $img_end) > $maxlen ) {
370                                         if($textlen < $maxlen) {
371                                                 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
372                                                 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
373                                                 $textlen = $maxlen;
374                                         }
375                                 }
376                                 else {
377                                         $new_body = $new_body . substr($orig_body, 0, $img_end);
378                                         $textlen += $img_end;
379                                 }
380                         }
381                         $orig_body = substr($orig_body, $img_end);
382
383                         if($orig_body === false) // in case the body ends on a closing image tag
384                                 $orig_body = '';
385
386                         $img_start = strpos($orig_body, '[img');
387                         $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
388                         $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
389                 }
390
391                 if( ($textlen + strlen($orig_body)) > $maxlen) {
392                         if($textlen < $maxlen) {
393                                 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
394                                 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
395                                 $textlen = $maxlen;
396                         }
397                 }
398                 else {
399                         logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
400                         $new_body = $new_body . $orig_body;
401                         $textlen += strlen($orig_body);
402                 }
403
404                 return $new_body;
405         }
406         else
407                 return $body;
408 }}
409
410 function title_is_body($title, $body) {
411
412         $title = strip_tags($title);
413         $title = trim($title);
414         $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
415         $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
416
417         $body = strip_tags($body);
418         $body = trim($body);
419         $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
420         $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
421
422         if (strlen($title) < strlen($body))
423                 $body = substr($body, 0, strlen($title));
424
425         if (($title != $body) and (substr($title, -3) == "...")) {
426                 $pos = strrpos($title, "...");
427                 if ($pos > 0) {
428                         $title = substr($title, 0, $pos);
429                         $body = substr($body, 0, $pos);
430                 }
431         }
432
433         return($title == $body);
434 }
435
436
437
438 function get_atom_elements($feed, $item, $contact = array()) {
439
440         require_once('library/HTMLPurifier.auto.php');
441         require_once('include/html2bbcode.php');
442
443         $best_photo = array();
444
445         $res = array();
446
447         $author = $item->get_author();
448         if($author) {
449                 $res['author-name'] = unxmlify($author->get_name());
450                 $res['author-link'] = unxmlify($author->get_link());
451         }
452         else {
453                 $res['author-name'] = unxmlify($feed->get_title());
454                 $res['author-link'] = unxmlify($feed->get_permalink());
455         }
456         $res['uri'] = unxmlify($item->get_id());
457         $res['title'] = unxmlify($item->get_title());
458         $res['body'] = unxmlify($item->get_content());
459         $res['plink'] = unxmlify($item->get_link(0));
460
461         if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
462                 logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
463                 $res['title'] = "";
464                 $res['body'] = nl2br($res['body']);
465         }
466
467         // removing the content of the title if its identically to the body
468         // This helps with auto generated titles e.g. from tumblr
469         if (title_is_body($res["title"], $res["body"]))
470                 $res['title'] = "";
471
472         if($res['plink'])
473                 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
474         else
475                 $base_url = '';
476
477         // look for a photo. We should check media size and find the best one,
478         // but for now let's just find any author photo
479         // Additionally we look for an alternate author link. On OStatus this one is the one we want.
480
481         // Search for ostatus conversation url
482         $authorlinks = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"]["http://www.w3.org/2005/Atom"]["link"];
483         if (is_array($authorlinks)) {
484                 foreach ($authorlinks as $link) {
485                         $linkdata = array_shift($link["attribs"]);
486
487                         if ($linkdata["rel"] == "alternate")
488                                 $res["author-link"] = $linkdata["href"];
489                 };
490         }
491
492         $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
493
494         if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
495                 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
496                 foreach($base as $link) {
497                         if($link['attribs']['']['rel'] === 'alternate')
498                                 $res['author-link'] = unxmlify($link['attribs']['']['href']);
499
500                         if(!x($res, 'author-avatar') || !$res['author-avatar']) {
501                                 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
502                                         $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
503                         }
504                 }
505         }
506
507         $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
508
509         if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
510                 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
511                 if($base && count($base)) {
512                         foreach($base as $link) {
513                                 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
514                                         $res['author-link'] = unxmlify($link['attribs']['']['href']);
515                                 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
516                                         if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
517                                                 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
518                                 }
519                         }
520                 }
521         }
522
523         // No photo/profile-link on the item - look at the feed level
524
525         if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
526                 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
527                 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
528                         $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
529                         foreach($base as $link) {
530                                 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
531                                         $res['author-link'] = unxmlify($link['attribs']['']['href']);
532                                 if(! $res['author-avatar']) {
533                                         if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
534                                                 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
535                                 }
536                         }
537                 }
538
539                 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
540
541                 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
542                         $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
543
544                         if($base && count($base)) {
545                                 foreach($base as $link) {
546                                         if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
547                                                 $res['author-link'] = unxmlify($link['attribs']['']['href']);
548                                         if(! (x($res,'author-avatar'))) {
549                                                 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
550                                                         $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
551                                         }
552                                 }
553                         }
554                 }
555         }
556
557         $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
558         if($apps && $apps[0]['attribs']['']['source']) {
559                 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
560                 if($res['app'] === 'web')
561                         $res['app'] = 'OStatus';
562         }
563
564         // base64 encoded json structure representing Diaspora signature
565
566         $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
567         if($dsig) {
568                 $res['dsprsig'] = unxmlify($dsig[0]['data']);
569         }
570
571         $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
572         if($dguid)
573                 $res['guid'] = unxmlify($dguid[0]['data']);
574
575         $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
576         if($bm)
577                 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
578
579
580         /**
581          * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
582          */
583
584         $have_real_body = false;
585
586         $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
587         if($rawenv) {
588                 $have_real_body = true;
589                 $res['body'] = $rawenv[0]['data'];
590                 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
591                 // make sure nobody is trying to sneak some html tags by us
592                 $res['body'] = notags(base64url_decode($res['body']));
593         }
594
595
596         $res['body'] = limit_body_size($res['body']);
597
598         // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
599         // the content type. Our own network only emits text normally, though it might have been converted to
600         // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
601         // have to assume it is all html and needs to be purified.
602
603         // It doesn't matter all that much security wise - because before this content is used anywhere, we are
604         // going to escape any tags we find regardless, but this lets us import a limited subset of html from
605         // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
606         // html.
607
608         if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
609
610                 $res['body'] = reltoabs($res['body'],$base_url);
611
612                 $res['body'] = html2bb_video($res['body']);
613
614                 $res['body'] = oembed_html2bbcode($res['body']);
615
616                 $config = HTMLPurifier_Config::createDefault();
617                 $config->set('Cache.DefinitionImpl', null);
618
619                 // we shouldn't need a whitelist, because the bbcode converter
620                 // will strip out any unsupported tags.
621
622                 $purifier = new HTMLPurifier($config);
623                 $res['body'] = $purifier->purify($res['body']);
624
625                 $res['body'] = @html2bbcode($res['body']);
626
627
628         }
629         elseif(! $have_real_body) {
630
631                 // it's not one of our messages and it has no tags
632                 // so it's probably just text. We'll escape it just to be safe.
633
634                 $res['body'] = escape_tags($res['body']);
635         }
636
637
638         // this tag is obsolete but we keep it for really old sites
639
640         $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
641         if($allow && $allow[0]['data'] == 1)
642                 $res['last-child'] = 1;
643         else
644                 $res['last-child'] = 0;
645
646         $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
647         if($private && intval($private[0]['data']) > 0)
648                 $res['private'] = intval($private[0]['data']);
649         else
650                 $res['private'] = 0;
651
652         $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
653         if($extid && $extid[0]['data'])
654                 $res['extid'] = $extid[0]['data'];
655
656         $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
657         if($rawlocation)
658                 $res['location'] = unxmlify($rawlocation[0]['data']);
659
660
661         $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
662         if($rawcreated)
663                 $res['created'] = unxmlify($rawcreated[0]['data']);
664
665
666         $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
667         if($rawedited)
668                 $res['edited'] = unxmlify($rawedited[0]['data']);
669
670         if((x($res,'edited')) && (! (x($res,'created'))))
671                 $res['created'] = $res['edited'];
672
673         if(! $res['created'])
674                 $res['created'] = $item->get_date('c');
675
676         if(! $res['edited'])
677                 $res['edited'] = $item->get_date('c');
678
679
680         // Disallow time travelling posts
681
682         $d1 = strtotime($res['created']);
683         $d2 = strtotime($res['edited']);
684         $d3 = strtotime('now');
685
686         if($d1 > $d3)
687                 $res['created'] = datetime_convert();
688         if($d2 > $d3)
689                 $res['edited'] = datetime_convert();
690
691         $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
692         if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
693                 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
694         elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
695                 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
696         if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
697                 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
698         elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
699                 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
700
701         if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
702                 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
703
704                 foreach($base as $link) {
705                         if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
706                                 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
707                                         $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
708                         }
709                 }
710         }
711
712         $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
713         if($rawgeo)
714                 $res['coord'] = unxmlify($rawgeo[0]['data']);
715
716         if ($contact["network"] == NETWORK_FEED) {
717                 $res['verb'] = ACTIVITY_POST;
718                 $res['object-type'] = ACTIVITY_OBJ_NOTE;
719         }
720
721         $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
722
723         // select between supported verbs
724
725         if($rawverb) {
726                 $res['verb'] = unxmlify($rawverb[0]['data']);
727         }
728
729         // translate OStatus unfollow to activity streams if it happened to get selected
730
731         if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
732                 $res['verb'] = ACTIVITY_UNFOLLOW;
733
734         $cats = $item->get_categories();
735         if($cats) {
736                 $tag_arr = array();
737                 foreach($cats as $cat) {
738                         $term = $cat->get_term();
739                         if(! $term)
740                                 $term = $cat->get_label();
741                         $scheme = $cat->get_scheme();
742                         if($scheme && $term && stristr($scheme,'X-DFRN:'))
743                                 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
744                         elseif($term)
745                                 $tag_arr[] = notags(trim($term));
746                 }
747                 $res['tag'] =  implode(',', $tag_arr);
748         }
749
750         $attach = $item->get_enclosures();
751         if($attach) {
752                 $att_arr = array();
753                 foreach($attach as $att) {
754                         $len   = intval($att->get_length());
755                         $link  = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
756                         $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
757                         $type  = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
758                         if(strpos($type,';'))
759                                 $type = substr($type,0,strpos($type,';'));
760                         if((! $link) || (strpos($link,'http') !== 0))
761                                 continue;
762
763                         if(! $title)
764                                 $title = ' ';
765                         if(! $type)
766                                 $type = 'application/octet-stream';
767
768                         $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
769                 }
770                 $res['attach'] = implode(',', $att_arr);
771         }
772
773         $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
774
775         if($rawobj) {
776                 $res['object'] = '<object>' . "\n";
777                 $child = $rawobj[0]['child'];
778                 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
779                         $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
780                         $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
781                 }
782                 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
783                         $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
784                 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
785                         $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
786                 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
787                         $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
788                 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
789                         $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
790                         if(! $body)
791                                 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
792                         // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
793                         $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
794                         if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
795
796                                 $body = html2bb_video($body);
797
798                                 $config = HTMLPurifier_Config::createDefault();
799                                 $config->set('Cache.DefinitionImpl', null);
800
801                                 $purifier = new HTMLPurifier($config);
802                                 $body = $purifier->purify($body);
803                                 $body = html2bbcode($body);
804                         }
805
806                         $res['object'] .= '<content>' . $body . '</content>' . "\n";
807                 }
808
809                 $res['object'] .= '</object>' . "\n";
810         }
811
812         $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
813
814         if($rawobj) {
815                 $res['target'] = '<target>' . "\n";
816                 $child = $rawobj[0]['child'];
817                 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
818                         $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
819                 }
820                 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
821                         $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
822                 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
823                         $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
824                 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
825                         $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
826                 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
827                         $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
828                         if(! $body)
829                                 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
830                         // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
831                         $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
832                         if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
833
834                                 $body = html2bb_video($body);
835
836                                 $config = HTMLPurifier_Config::createDefault();
837                                 $config->set('Cache.DefinitionImpl', null);
838
839                                 $purifier = new HTMLPurifier($config);
840                                 $body = $purifier->purify($body);
841                                 $body = html2bbcode($body);
842                         }
843
844                         $res['target'] .= '<content>' . $body . '</content>' . "\n";
845                 }
846
847                 $res['target'] .= '</target>' . "\n";
848         }
849
850         // This is some experimental stuff. By now retweets are shown with "RT:"
851         // But: There is data so that the message could be shown similar to native retweets
852         // There is some better way to parse this array - but it didn't worked for me.
853         $child = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["entry"][0]["child"]["http://activitystrea.ms/spec/1.0/"][object][0]["child"];
854         if (is_array($child)) {
855                 logger('get_atom_elements: Looking for status.net repeated message');
856
857                 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
858                 $orig_id = ostatus_convert_href($child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"]);
859                 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
860                 $uri = $author["uri"][0]["data"];
861                 $name = $author["name"][0]["data"];
862                 $avatar = @array_shift($author["link"][2]["attribs"]);
863                 $avatar = $avatar["href"];
864
865                 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
866                         logger('get_atom_elements: fixing sender of repeated message. '.$orig_id, LOGGER_DEBUG);
867
868                         if (!intval(get_config('system','wall-to-wall_share'))) {
869                                 $prefix = share_header($name, $uri, $avatar, "", "", $orig_link);
870
871                                 $res["body"] = $prefix.html2bbcode($message)."[/share]";
872                         } else {
873                                 $res["owner-name"] = $res["author-name"];
874                                 $res["owner-link"] = $res["author-link"];
875                                 $res["owner-avatar"] = $res["author-avatar"];
876
877                                 $res["author-name"] = $name;
878                                 $res["author-link"] = $uri;
879                                 $res["author-avatar"] = $avatar;
880
881                                 $res["body"] = html2bbcode($message);
882                         }
883                 }
884         }
885
886         // Search for ostatus conversation url
887         $links = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["entry"][0]["child"]["http://www.w3.org/2005/Atom"]["link"];
888
889         if (is_array($links)) {
890                 foreach ($links as $link) {
891                         $conversation = array_shift($link["attribs"]);
892
893                         if ($conversation["rel"] == "ostatus:conversation") {
894                                 $res["ostatus_conversation"] = ostatus_convert_href($conversation["href"]);
895                                 logger('get_atom_elements: found conversation url '.$res["ostatus_conversation"]);
896                         } elseif ($conversation["rel"] == "alternate") {
897                                 $res["plink"] = $conversation["href"];
898                                 logger('get_atom_elements: found plink '.$res["plink"]);
899                         }
900                 };
901         }
902
903         if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
904                 $preview = "";
905
906                 // Handle enclosures and treat them as preview picture
907                 if (isset($attach))
908                         foreach ($attach AS $attachment)
909                                 if ($attachment->type == "image/jpeg")
910                                         $preview = $attachment->link;
911
912                 $res["body"] = $res["title"].add_page_info($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
913                 $res["tag"] = add_page_keywords($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
914                 $res["title"] = "";
915                 $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
916                 unset($res["attach"]);
917         } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
918                 $res["body"] = add_page_info_to_body($res["body"]);
919         elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
920                 $res["body"] = add_page_info_to_body($res["body"]);
921         }
922
923         $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
924
925         call_hooks('parse_atom', $arr);
926
927         return $res;
928 }
929
930 function add_page_info_data($data) {
931         call_hooks('page_info_data', $data);
932
933         // It maybe is a rich content, but if it does have everything that a link has,
934         // then treat it that way
935         if (($data["type"] == "rich") AND is_string($data["title"]) AND
936                 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
937                 $data["type"] = "link";
938
939         if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
940                 return("");
941
942         if ($no_photos AND ($data["type"] == "photo"))
943                 return("");
944
945         // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
946         if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
947                 require_once("include/network.php");
948                 $data["url"] = short_link($data["url"]);
949         }
950
951         if (($data["type"] != "photo") AND is_string($data["title"]))
952                 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
953
954         if (($data["type"] != "video") AND ($photo != ""))
955                 $text .= '[img]'.$photo.'[/img]';
956         elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
957                 $imagedata = $data["images"][0];
958                 $text .= '[img]'.$imagedata["src"].'[/img]';
959         }
960
961         if (($data["type"] != "photo") AND is_string($data["text"]))
962                 $text .= "[quote]".$data["text"]."[/quote]";
963
964         $hashtags = "";
965         if (isset($data["keywords"]) AND count($data["keywords"])) {
966                 $a = get_app();
967                 $hashtags = "\n";
968                 foreach ($data["keywords"] AS $keyword) {
969                         $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
970                                                 array("","", "", "", "", ""), $keyword);
971                         $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
972                 }
973         }
974
975         return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
976 }
977
978 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
979         require_once("mod/parse_url.php");
980
981         $data = Cache::get("parse_url:".$url);
982         if (is_null($data)){
983                 $data = parseurl_getsiteinfo($url, true);
984                 Cache::set("parse_url:".$url,serialize($data));
985         } else
986                 $data = unserialize($data);
987
988         if ($photo != "")
989                 $data["images"][0]["src"] = $photo;
990
991         logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
992
993         if (!$keywords AND isset($data["keywords"]))
994                 unset($data["keywords"]);
995
996         if (($keyword_blacklist != "") AND isset($data["keywords"])) {
997                 $list = explode(",", $keyword_blacklist);
998                 foreach ($list AS $keyword) {
999                         $keyword = trim($keyword);
1000                         $index = array_search($keyword, $data["keywords"]);
1001                         if ($index !== false)
1002                                 unset($data["keywords"][$index]);
1003                 }
1004         }
1005
1006         return($data);
1007 }
1008
1009 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
1010         $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1011
1012         $tags = "";
1013         if (isset($data["keywords"]) AND count($data["keywords"])) {
1014                 $a = get_app();
1015                 foreach ($data["keywords"] AS $keyword) {
1016                         $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
1017                                                 array("","", "", "", "", ""), $keyword);
1018
1019                         if ($tags != "")
1020                                 $tags .= ",";
1021
1022                         $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
1023                 }
1024         }
1025
1026         return($tags);
1027 }
1028
1029 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
1030         $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1031
1032         $text = add_page_info_data($data);
1033
1034         return($text);
1035 }
1036
1037 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
1038
1039         logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
1040
1041         $URLSearchString = "^\[\]";
1042
1043         // Adding these spaces is a quick hack due to my problems with regular expressions :)
1044         preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
1045
1046         if (!$matches)
1047                 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
1048
1049         // Convert urls without bbcode elements
1050         if (!$matches AND $texturl) {
1051                 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
1052
1053                 // Yeah, a hack. I really hate regular expressions :)
1054                 if ($matches)
1055                         $matches[1] = $matches[2];
1056         }
1057
1058         if ($matches)
1059                 $footer = add_page_info($matches[1], $no_photos);
1060
1061         // Remove the link from the body if the link is attached at the end of the post
1062         if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
1063                 $removedlink = trim(str_replace($matches[1], "", $body));
1064                 if (($removedlink == "") OR strstr($body, $removedlink))
1065                         $body = $removedlink;
1066
1067                 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
1068                 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
1069                 if (($removedlink == "") OR strstr($body, $removedlink))
1070                         $body = $removedlink;
1071         }
1072
1073         // Add the page information to the bottom
1074         if (isset($footer) AND (trim($footer) != ""))
1075                 $body .= $footer;
1076
1077         return $body;
1078 }
1079
1080 function encode_rel_links($links) {
1081         $o = '';
1082         if(! ((is_array($links)) && (count($links))))
1083                 return $o;
1084         foreach($links as $link) {
1085                 $o .= '<link ';
1086                 if($link['attribs']['']['rel'])
1087                         $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
1088                 if($link['attribs']['']['type'])
1089                         $o .= 'type="' . $link['attribs']['']['type'] . '" ';
1090                 if($link['attribs']['']['href'])
1091                         $o .= 'href="' . $link['attribs']['']['href'] . '" ';
1092                 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
1093                         $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
1094                 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
1095                         $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
1096                 $o .= ' />' . "\n" ;
1097         }
1098         return xmlify($o);
1099 }
1100
1101
1102
1103 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
1104
1105         // If it is a posting where users should get notifications, then define it as wall posting
1106         if ($notify) {
1107                 $arr['wall'] = 1;
1108                 $arr['type'] = 'wall';
1109                 $arr['origin'] = 1;
1110                 $arr['last-child'] = 1;
1111                 $arr['network'] = NETWORK_DFRN;
1112         }
1113
1114         // If a Diaspora signature structure was passed in, pull it out of the
1115         // item array and set it aside for later storage.
1116
1117         $dsprsig = null;
1118         if(x($arr,'dsprsig')) {
1119                 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1120                 unset($arr['dsprsig']);
1121         }
1122
1123         // Converting the plink
1124         if ($arr['network'] == NETWORK_OSTATUS) {
1125                 if (isset($arr['plink']))
1126                         $arr['plink'] = ostatus_convert_href($arr['plink']);
1127                 elseif (isset($arr['uri']))
1128                         $arr['plink'] = ostatus_convert_href($arr['uri']);
1129         }
1130
1131         // if an OStatus conversation url was passed in, it is stored and then
1132         // removed from the array.
1133         $ostatus_conversation = null;
1134
1135         if (isset($arr["ostatus_conversation"])) {
1136                 $ostatus_conversation = $arr["ostatus_conversation"];
1137                 unset($arr["ostatus_conversation"]);
1138         }
1139
1140         if(x($arr, 'gravity'))
1141                 $arr['gravity'] = intval($arr['gravity']);
1142         elseif($arr['parent-uri'] === $arr['uri'])
1143                 $arr['gravity'] = 0;
1144         elseif(activity_match($arr['verb'],ACTIVITY_POST))
1145                 $arr['gravity'] = 6;
1146         else
1147                 $arr['gravity'] = 6;   // extensible catchall
1148
1149         if(! x($arr,'type'))
1150                 $arr['type']      = 'remote';
1151
1152
1153
1154         /* check for create  date and expire time */
1155         $uid = intval($arr['uid']);
1156         $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
1157         if(count($r)) {
1158                 $expire_interval = $r[0]['expire'];
1159                 if ($expire_interval>0) {
1160                         $expire_date =  new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1161                         $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1162                         if ($created_date < $expire_date) {
1163                                 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1164                                 return 0;
1165                         }
1166                 }
1167         }
1168
1169         // If there is no guid then take the same guid that was taken before for the same uri
1170         if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "")) {
1171                 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1172                 $r = q("SELECT `guid` FROM `item` WHERE `uri` = '%s' AND `guid` != '' LIMIT 1",
1173                         dbesc(trim($arr['uri']))
1174                 );
1175
1176                 if(count($r)) {
1177                         $arr['guid'] = $r[0]["guid"];
1178                         logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1179                 }
1180         }
1181
1182         // If there is no guid then take the same guid that was taken before for the same plink
1183         if ((trim($arr['guid']) == "") AND (trim($arr['plink']) != "")) {
1184                 logger('item_store: checking for an existing guid for plink '.$arr['plink'], LOGGER_DEBUG);
1185                 $r = q("SELECT `guid` FROM `item` WHERE `plink` = '%s' AND `guid` != '' LIMIT 1",
1186                         dbesc(trim($arr['plink']))
1187                 );
1188
1189                 if(count($r)) {
1190                         $arr['guid'] = $r[0]["guid"];
1191                         logger('item_store: found guid '.$arr['guid'].' for plink '.$arr['plink'], LOGGER_DEBUG);
1192                 }
1193         }
1194
1195         // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1196         // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1197         //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1198         //      $arr['body'] = strip_tags($arr['body']);
1199
1200
1201         if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1202                 require_once('library/langdet/Text/LanguageDetect.php');
1203                 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1204                 $l = new Text_LanguageDetect;
1205                 //$lng = $l->detectConfidence($naked_body);
1206                 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1207                 $lng = $l->detect($naked_body, 3);
1208
1209                 if (sizeof($lng) > 0) {
1210                         $postopts = "";
1211
1212                         foreach ($lng as $language => $score) {
1213                                 if ($postopts == "")
1214                                         $postopts = "lang=";
1215                                 else
1216                                         $postopts .= ":";
1217
1218                                 $postopts .= $language.";".$score;
1219                         }
1220                         $arr['postopts'] = $postopts;
1221                 }
1222         }
1223
1224         $arr['wall']          = ((x($arr,'wall'))          ? intval($arr['wall'])                : 0);
1225         $arr['uri']           = ((x($arr,'uri'))           ? notags(trim($arr['uri']))           : random_string());
1226         $arr['extid']         = ((x($arr,'extid'))         ? notags(trim($arr['extid']))         : '');
1227         $arr['author-name']   = ((x($arr,'author-name'))   ? notags(trim($arr['author-name']))   : '');
1228         $arr['author-link']   = ((x($arr,'author-link'))   ? notags(trim($arr['author-link']))   : '');
1229         $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1230         $arr['owner-name']    = ((x($arr,'owner-name'))    ? notags(trim($arr['owner-name']))    : '');
1231         $arr['owner-link']    = ((x($arr,'owner-link'))    ? notags(trim($arr['owner-link']))    : '');
1232         $arr['owner-avatar']  = ((x($arr,'owner-avatar'))  ? notags(trim($arr['owner-avatar']))  : '');
1233         $arr['created']       = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1234         $arr['edited']        = ((x($arr,'edited')  !== false) ? datetime_convert('UTC','UTC',$arr['edited'])  : datetime_convert());
1235         $arr['commented']     = ((x($arr,'commented')  !== false) ? datetime_convert('UTC','UTC',$arr['commented'])  : datetime_convert());
1236         $arr['received']      = ((x($arr,'received')  !== false) ? datetime_convert('UTC','UTC',$arr['received'])  : datetime_convert());
1237         $arr['changed']       = ((x($arr,'changed')  !== false) ? datetime_convert('UTC','UTC',$arr['changed'])  : datetime_convert());
1238         $arr['title']         = ((x($arr,'title'))         ? notags(trim($arr['title']))         : '');
1239         $arr['location']      = ((x($arr,'location'))      ? notags(trim($arr['location']))      : '');
1240         $arr['coord']         = ((x($arr,'coord'))         ? notags(trim($arr['coord']))         : '');
1241         $arr['last-child']    = ((x($arr,'last-child'))    ? intval($arr['last-child'])          : 0 );
1242         $arr['visible']       = ((x($arr,'visible') !== false) ? intval($arr['visible'])         : 1 );
1243         $arr['deleted']       = 0;
1244         $arr['parent-uri']    = ((x($arr,'parent-uri'))    ? notags(trim($arr['parent-uri']))    : '');
1245         $arr['verb']          = ((x($arr,'verb'))          ? notags(trim($arr['verb']))          : '');
1246         $arr['object-type']   = ((x($arr,'object-type'))   ? notags(trim($arr['object-type']))   : '');
1247         $arr['object']        = ((x($arr,'object'))        ? trim($arr['object'])                : '');
1248         $arr['target-type']   = ((x($arr,'target-type'))   ? notags(trim($arr['target-type']))   : '');
1249         $arr['target']        = ((x($arr,'target'))        ? trim($arr['target'])                : '');
1250         $arr['plink']         = ((x($arr,'plink'))         ? notags(trim($arr['plink']))         : '');
1251         $arr['allow_cid']     = ((x($arr,'allow_cid'))     ? trim($arr['allow_cid'])             : '');
1252         $arr['allow_gid']     = ((x($arr,'allow_gid'))     ? trim($arr['allow_gid'])             : '');
1253         $arr['deny_cid']      = ((x($arr,'deny_cid'))      ? trim($arr['deny_cid'])              : '');
1254         $arr['deny_gid']      = ((x($arr,'deny_gid'))      ? trim($arr['deny_gid'])              : '');
1255         $arr['private']       = ((x($arr,'private'))       ? intval($arr['private'])             : 0 );
1256         $arr['bookmark']      = ((x($arr,'bookmark'))      ? intval($arr['bookmark'])            : 0 );
1257         $arr['body']          = ((x($arr,'body'))          ? trim($arr['body'])                  : '');
1258         $arr['tag']           = ((x($arr,'tag'))           ? notags(trim($arr['tag']))           : '');
1259         $arr['attach']        = ((x($arr,'attach'))        ? notags(trim($arr['attach']))        : '');
1260         $arr['app']           = ((x($arr,'app'))           ? notags(trim($arr['app']))           : '');
1261         $arr['origin']        = ((x($arr,'origin'))        ? intval($arr['origin'])              : 0 );
1262         $arr['network']       = ((x($arr,'network'))       ? trim($arr['network'])               : '');
1263         $arr['guid']          = ((x($arr,'guid'))          ? notags(trim($arr['guid']))          : get_guid(32, $arr['network']));
1264         $arr['postopts']      = ((x($arr,'postopts'))      ? trim($arr['postopts'])              : '');
1265         $arr['resource-id']   = ((x($arr,'resource-id'))   ? trim($arr['resource-id'])           : '');
1266         $arr['event-id']      = ((x($arr,'event-id'))      ? intval($arr['event-id'])            : 0 );
1267         $arr['inform']        = ((x($arr,'inform'))        ? trim($arr['inform'])                : '');
1268         $arr['file']          = ((x($arr,'file'))          ? trim($arr['file'])                  : '');
1269
1270         if ($arr['plink'] == "") {
1271                 $a = get_app();
1272                 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1273         }
1274
1275         if ($arr['network'] == "") {
1276                 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1277                         intval($arr['contact-id']),
1278                         intval($arr['uid'])
1279                 );
1280
1281                 if(count($r))
1282                         $arr['network'] = $r[0]["network"];
1283
1284                 // Fallback to friendica (why is it empty in some cases?)
1285                 if ($arr['network'] == "")
1286                         $arr['network'] = NETWORK_DFRN;
1287
1288                 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1289         }
1290
1291         if ($arr['guid'] != "") {
1292                 // Checking if there is already an item with the same guid
1293                 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1294                 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1295                         dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1296
1297                 if(count($r)) {
1298                         logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1299                         return 0;
1300                 }
1301         }
1302
1303         // Check for hashtags in the body and repair or add hashtag links
1304         item_body_set_hashtags($arr);
1305
1306         $arr['thr-parent'] = $arr['parent-uri'];
1307         if($arr['parent-uri'] === $arr['uri']) {
1308                 $parent_id = 0;
1309                 $parent_deleted = 0;
1310                 $allow_cid = $arr['allow_cid'];
1311                 $allow_gid = $arr['allow_gid'];
1312                 $deny_cid  = $arr['deny_cid'];
1313                 $deny_gid  = $arr['deny_gid'];
1314                 $notify_type = 'wall-new';
1315         }
1316         else {
1317
1318                 // find the parent and snarf the item id and ACLs
1319                 // and anything else we need to inherit
1320
1321                 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1322                         dbesc($arr['parent-uri']),
1323                         intval($arr['uid'])
1324                 );
1325
1326                 if(count($r)) {
1327
1328                         // is the new message multi-level threaded?
1329                         // even though we don't support it now, preserve the info
1330                         // and re-attach to the conversation parent.
1331
1332                         if($r[0]['uri'] != $r[0]['parent-uri']) {
1333                                 $arr['parent-uri'] = $r[0]['parent-uri'];
1334                                 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1335                                         ORDER BY `id` ASC LIMIT 1",
1336                                         dbesc($r[0]['parent-uri']),
1337                                         dbesc($r[0]['parent-uri']),
1338                                         intval($arr['uid'])
1339                                 );
1340                                 if($z && count($z))
1341                                         $r = $z;
1342                         }
1343
1344                         $parent_id      = $r[0]['id'];
1345                         $parent_deleted = $r[0]['deleted'];
1346                         $allow_cid      = $r[0]['allow_cid'];
1347                         $allow_gid      = $r[0]['allow_gid'];
1348                         $deny_cid       = $r[0]['deny_cid'];
1349                         $deny_gid       = $r[0]['deny_gid'];
1350                         $arr['wall']    = $r[0]['wall'];
1351                         $notify_type    = 'comment-new';
1352
1353                         // if the parent is private, force privacy for the entire conversation
1354                         // This differs from the above settings as it subtly allows comments from
1355                         // email correspondents to be private even if the overall thread is not.
1356
1357                         if($r[0]['private'])
1358                                 $arr['private'] = $r[0]['private'];
1359
1360                         // Edge case. We host a public forum that was originally posted to privately.
1361                         // The original author commented, but as this is a comment, the permissions
1362                         // weren't fixed up so it will still show the comment as private unless we fix it here.
1363
1364                         if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1365                                 $arr['private'] = 0;
1366
1367
1368                         // If its a post from myself then tag the thread as "mention"
1369                         logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1370                         $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1371                         if(count($u)) {
1372                                 $a = get_app();
1373                                 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1374                                 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1375                                 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1376                                         q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1377                                         logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1378                                 }
1379                         }
1380                 }
1381                 else {
1382
1383                         // Allow one to see reply tweets from status.net even when
1384                         // we don't have or can't see the original post.
1385
1386                         if($force_parent) {
1387                                 logger('item_store: $force_parent=true, reply converted to top-level post.');
1388                                 $parent_id = 0;
1389                                 $arr['parent-uri'] = $arr['uri'];
1390                                 $arr['gravity'] = 0;
1391                         }
1392                         else {
1393                                 logger('item_store: item parent was not found - ignoring item');
1394                                 return 0;
1395                         }
1396
1397                         $parent_deleted = 0;
1398                 }
1399         }
1400
1401         $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` = '%s' AND `uid` = %d LIMIT 1",
1402                 dbesc($arr['uri']),
1403                 dbesc($arr['network']),
1404                 intval($arr['uid'])
1405         );
1406         if($r && count($r)) {
1407                 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1408                 return 0;
1409         }
1410
1411         $r = q("SELECT `id` FROM `item` WHERE `plink` = '%s' AND `network` = '%s' AND `uid` = %d LIMIT 1",
1412                 dbesc($arr['plink']),
1413                 dbesc($arr['network']),
1414                 intval($arr['uid'])
1415         );
1416         if($r && count($r)) {
1417                 logger('duplicated item with the same plink found. ' . print_r($arr,true));
1418                 return 0;
1419         }
1420
1421         // Check for an existing post with the same content. There seems to be a problem with OStatus.
1422         $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1423                 dbesc($arr['body']),
1424                 dbesc($arr['network']),
1425                 dbesc($arr['created']),
1426                 intval($arr['contact-id']),
1427                 intval($arr['uid'])
1428         );
1429         if($r && count($r)) {
1430                 logger('duplicated item with the same body found. ' . print_r($arr,true));
1431                 return 0;
1432         }
1433
1434         // Is this item available in the global items (with uid=0)?
1435         if ($arr["uid"] == 0) {
1436                 $arr["global"] = true;
1437
1438                 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1439         }  else {
1440                 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1441
1442                 $arr["global"] = (count($isglobal) > 0);
1443         }
1444
1445         // Fill the cache field
1446         put_item_in_cache($arr);
1447
1448         call_hooks('post_remote',$arr);
1449
1450         if(x($arr,'cancel')) {
1451                 logger('item_store: post cancelled by plugin.');
1452                 return 0;
1453         }
1454
1455         // Store the unescaped version
1456         $unescaped = $arr;
1457
1458         dbesc_array($arr);
1459
1460         logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1461
1462         $r = dbq("INSERT INTO `item` (`"
1463                         . implode("`, `", array_keys($arr))
1464                         . "`) VALUES ('"
1465                         . implode("', '", array_values($arr))
1466                         . "')" );
1467
1468         // And restore it
1469         $arr = $unescaped;
1470
1471         // find the item we just created
1472         $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1473                 dbesc($arr['uri']),
1474                 intval($arr['uid'])
1475         );
1476
1477         if(count($r)) {
1478                 $current_post = $r[0]['id'];
1479                 logger('item_store: created item ' . $current_post);
1480
1481                 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1482                 // This can be used to filter for inactive contacts.
1483                 // Only do this for public postings to avoid privacy problems, since poco data is public.
1484                 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1485
1486                 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1487
1488                 // Is it a forum? Then we don't care about the rules from above
1489                 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1490                         $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1491                                         intval($arr['contact-id']));
1492                         if ($isforum)
1493                                 $update = true;
1494                 }
1495
1496                 if ($update)
1497                         q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1498                                 dbesc($arr['received']),
1499                                 dbesc($arr['received']),
1500                                 intval($arr['contact-id'])
1501                         );
1502         } else {
1503                 logger('item_store: could not locate created item');
1504                 return 0;
1505         }
1506         if(count($r) > 1) {
1507                 logger('item_store: duplicated post occurred. Removing duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1508                 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1509                         dbesc($arr['uri']),
1510                         intval($arr['uid']),
1511                         intval($current_post)
1512                 );
1513         }
1514
1515         if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1516                 $parent_id = $current_post;
1517
1518         if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1519                 $private = 1;
1520         else
1521                 $private = $arr['private'];
1522
1523         // Set parent id - and also make sure to inherit the parent's ACLs.
1524
1525         $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1526                 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1527                 intval($parent_id),
1528                 dbesc($allow_cid),
1529                 dbesc($allow_gid),
1530                 dbesc($deny_cid),
1531                 dbesc($deny_gid),
1532                 intval($private),
1533                 intval($parent_deleted),
1534                 intval($current_post)
1535         );
1536
1537         // Complete ostatus threads
1538         if ($ostatus_conversation)
1539                 complete_conversation($current_post, $ostatus_conversation);
1540
1541         $arr['id'] = $current_post;
1542         $arr['parent'] = $parent_id;
1543         $arr['allow_cid'] = $allow_cid;
1544         $arr['allow_gid'] = $allow_gid;
1545         $arr['deny_cid'] = $deny_cid;
1546         $arr['deny_gid'] = $deny_gid;
1547         $arr['private'] = $private;
1548         $arr['deleted'] = $parent_deleted;
1549
1550         // update the commented timestamp on the parent
1551         // Only update "commented" if it is really a comment
1552         if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1553                 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1554                         dbesc(datetime_convert()),
1555                         dbesc(datetime_convert()),
1556                         intval($parent_id)
1557                 );
1558         else
1559                 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1560                         dbesc(datetime_convert()),
1561                         intval($parent_id)
1562                 );
1563
1564         if($dsprsig) {
1565                 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1566                         intval($current_post),
1567                         dbesc($dsprsig->signed_text),
1568                         dbesc($dsprsig->signature),
1569                         dbesc($dsprsig->signer)
1570                 );
1571         }
1572
1573
1574         /**
1575          * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1576          */
1577
1578         if($arr['last-child']) {
1579                 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1580                         dbesc($arr['uri']),
1581                         intval($arr['uid']),
1582                         intval($current_post)
1583                 );
1584         }
1585
1586         $deleted = tag_deliver($arr['uid'],$current_post);
1587
1588         // current post can be deleted if is for a community page and no mention are
1589         // in it.
1590         if (!$deleted AND !$dontcache) {
1591
1592                 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1593                 if (count($r) == 1) {
1594                         call_hooks('post_remote_end', $r[0]);
1595                 } else
1596                         logger('item_store: new item not found in DB, id ' . $current_post);
1597         }
1598
1599         // Add every contact of the post to the global contact table
1600         poco_store($arr);
1601
1602         create_tags_from_item($current_post);
1603         create_files_from_item($current_post);
1604
1605         // Only check for notifications on start posts
1606         if ($arr['parent-uri'] === $arr['uri']) {
1607                 add_thread($current_post);
1608                 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1609
1610                 // Send a notification for every new post?
1611                 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1612                         intval($arr['contact-id']),
1613                         intval($arr['uid'])
1614                 );
1615                 $send_notification = count($r);
1616
1617                 if (!$send_notification) {
1618                         $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1619                                 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1620
1621                         if (count($tags)) {
1622                                 foreach ($tags AS $tag) {
1623                                         $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1624                                                 normalise_link($tag["url"]), intval($arr['uid']));
1625                                         if (count($r))
1626                                                 $send_notification = true;
1627                                 }
1628                         }
1629                 }
1630
1631                 if ($send_notification) {
1632                         logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1633                         $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1634                                 intval($arr['uid']));
1635
1636                         $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1637                                 intval($current_post),
1638                                 intval($arr['uid'])
1639                         );
1640
1641                         $a = get_app();
1642
1643                         require_once('include/enotify.php');
1644                         notification(array(
1645                                 'type'         => NOTIFY_SHARE,
1646                                 'notify_flags' => $u[0]['notify-flags'],
1647                                 'language'     => $u[0]['language'],
1648                                 'to_name'      => $u[0]['username'],
1649                                 'to_email'     => $u[0]['email'],
1650                                 'uid'          => $u[0]['uid'],
1651                                 'item'         => $item[0],
1652                                 'link'         => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1653                                 'source_name'  => $item[0]['author-name'],
1654                                 'source_link'  => $item[0]['author-link'],
1655                                 'source_photo' => $item[0]['author-avatar'],
1656                                 'verb'         => ACTIVITY_TAG,
1657                                 'otype'        => 'item',
1658                                 'parent'       => $arr['parent']
1659                         ));
1660                         logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1661                 }
1662         } else {
1663                 update_thread($parent_id);
1664                 add_shadow_entry($arr);
1665         }
1666
1667         if ($notify)
1668                 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1669
1670         return $current_post;
1671 }
1672
1673 function item_body_set_hashtags(&$item) {
1674
1675         $tags = get_tags($item["body"]);
1676
1677         // No hashtags?
1678         if(!count($tags))
1679                 return(false);
1680
1681         // This sorting is important when there are hashtags that are part of other hashtags
1682         // Otherwise there could be problems with hashtags like #test and #test2
1683         rsort($tags);
1684
1685         $a = get_app();
1686
1687         $URLSearchString = "^\[\]";
1688
1689         // All hashtags should point to the home server
1690         //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1691         //              "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1692
1693         //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1694         //              "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1695
1696         // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1697         $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1698                 function ($match){
1699                         return("[url=".$match[1]."]".str_replace("#", "&num;", $match[2])."[/url]");
1700                 },$item["body"]);
1701
1702         $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1703                 function ($match){
1704                         return("[bookmark=".$match[1]."]".str_replace("#", "&num;", $match[2])."[/bookmark]");
1705                 },$item["body"]);
1706
1707         $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1708                 function ($match){
1709                         return("[attachment ".str_replace("#", "&num;", $match[1])."]".$match[2]."[/attachment]");
1710                 },$item["body"]);
1711
1712         // Repair recursive urls
1713         $item["body"] = preg_replace("/&num;\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1714                         "&num;$2", $item["body"]);
1715
1716         foreach($tags as $tag) {
1717                 if(strpos($tag,'#') !== 0)
1718                         continue;
1719
1720                 if(strpos($tag,'[url='))
1721                         continue;
1722
1723                 $basetag = str_replace('_',' ',substr($tag,1));
1724
1725                 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1726
1727                 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1728
1729                 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1730                         if(strlen($item["tag"]))
1731                                 $item["tag"] = ','.$item["tag"];
1732                         $item["tag"] = $newtag.$item["tag"];
1733                 }
1734         }
1735
1736         // Convert back the masked hashtags
1737         $item["body"] = str_replace("&num;", "#", $item["body"]);
1738 }
1739
1740 function get_item_guid($id) {
1741         $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1742         if (count($r))
1743                 return($r[0]["guid"]);
1744         else
1745                 return("");
1746 }
1747
1748 function get_item_id($guid, $uid = 0) {
1749
1750         $nick = "";
1751         $id = 0;
1752
1753         if ($uid == 0)
1754                 $uid == local_user();
1755
1756         // Does the given user have this item?
1757         if ($uid) {
1758                 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1759                         WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1760                                 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1761                 if (count($r)) {
1762                         $id = $r[0]["id"];
1763                         $nick = $r[0]["nickname"];
1764                 }
1765         }
1766
1767         // Or is it anywhere on the server?
1768         if ($nick == "") {
1769                 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1770                         WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1771                                 AND `item`.`allow_cid` = ''  AND `item`.`allow_gid` = ''
1772                                 AND `item`.`deny_cid`  = '' AND `item`.`deny_gid`  = ''
1773                                 AND `item`.`private` = 0 AND `item`.`wall` = 1
1774                                 AND `item`.`guid` = '%s'", dbesc($guid));
1775                 if (count($r)) {
1776                         $id = $r[0]["id"];
1777                         $nick = $r[0]["nickname"];
1778                 }
1779         }
1780         return(array("nick" => $nick, "id" => $id));
1781 }
1782
1783 // return - test
1784 function get_item_contact($item,$contacts) {
1785         if(! count($contacts) || (! is_array($item)))
1786                 return false;
1787         foreach($contacts as $contact) {
1788                 if($contact['id'] == $item['contact-id']) {
1789                         return $contact;
1790                         break; // NOTREACHED
1791                 }
1792         }
1793         return false;
1794 }
1795
1796 /**
1797  * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1798  * @param int $uid
1799  * @param int $item_id
1800  * @return bool true if item was deleted, else false
1801  */
1802 function tag_deliver($uid,$item_id) {
1803
1804         //
1805
1806         $a = get_app();
1807
1808         $mention = false;
1809
1810         $u = q("select * from user where uid = %d limit 1",
1811                 intval($uid)
1812         );
1813         if(! count($u))
1814                 return;
1815
1816         $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1817         $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1818
1819
1820         $i = q("select * from item where id = %d and uid = %d limit 1",
1821                 intval($item_id),
1822                 intval($uid)
1823         );
1824         if(! count($i))
1825                 return;
1826
1827         $item = $i[0];
1828
1829         $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1830
1831         // Diaspora uses their own hardwired link URL in @-tags
1832         // instead of the one we supply with webfinger
1833
1834         $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1835
1836         $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1837         if($cnt) {
1838                 foreach($matches as $mtch) {
1839                         if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1840                                 $mention = true;
1841                                 logger('tag_deliver: mention found: ' . $mtch[2]);
1842                         }
1843                 }
1844         }
1845
1846         if(! $mention){
1847                 if ( ($community_page || $prvgroup) &&
1848                           (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1849                         // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1850                         // delete it!
1851                         logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1852                         q("DELETE FROM item WHERE id = %d and uid = %d",
1853                                 intval($item_id),
1854                                 intval($uid)
1855                         );
1856                         return true;
1857                 }
1858                 return;
1859         }
1860
1861
1862         // send a notification
1863
1864         // use a local photo if we have one
1865
1866         $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1867                 intval($u[0]['uid']),
1868                 dbesc(normalise_link($item['author-link']))
1869         );
1870         $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1871
1872
1873         require_once('include/enotify.php');
1874         notification(array(
1875                 'type'         => NOTIFY_TAGSELF,
1876                 'notify_flags' => $u[0]['notify-flags'],
1877                 'language'     => $u[0]['language'],
1878                 'to_name'      => $u[0]['username'],
1879                 'to_email'     => $u[0]['email'],
1880                 'uid'          => $u[0]['uid'],
1881                 'item'         => $item,
1882                 'link'         => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1883                 'source_name'  => $item['author-name'],
1884                 'source_link'  => $item['author-link'],
1885                 'source_photo' => $photo,
1886                 'verb'         => ACTIVITY_TAG,
1887                 'otype'        => 'item',
1888                 'parent'       => $item['parent']
1889         ));
1890
1891
1892         $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1893
1894         call_hooks('tagged', $arr);
1895
1896         if((! $community_page) && (! $prvgroup))
1897                 return;
1898
1899
1900         // tgroup delivery - setup a second delivery chain
1901         // prevent delivery looping - only proceed
1902         // if the message originated elsewhere and is a top-level post
1903
1904         if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1905                 return;
1906
1907         // now change this copy of the post to a forum head message and deliver to all the tgroup members
1908
1909
1910         $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1911                 intval($u[0]['uid'])
1912         );
1913         if(! count($c))
1914                 return;
1915
1916         // also reset all the privacy bits to the forum default permissions
1917
1918         $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1919
1920         $forum_mode = (($prvgroup) ? 2 : 1);
1921
1922         q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1923                 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s'  where id = %d",
1924                 intval($forum_mode),
1925                 dbesc($c[0]['name']),
1926                 dbesc($c[0]['url']),
1927                 dbesc($c[0]['thumb']),
1928                 intval($private),
1929                 dbesc($u[0]['allow_cid']),
1930                 dbesc($u[0]['allow_gid']),
1931                 dbesc($u[0]['deny_cid']),
1932                 dbesc($u[0]['deny_gid']),
1933                 intval($item_id)
1934         );
1935         update_thread($item_id);
1936
1937         proc_run('php','include/notifier.php','tgroup',$item_id);
1938
1939 }
1940
1941
1942
1943 function tgroup_check($uid,$item) {
1944
1945         $a = get_app();
1946
1947         $mention = false;
1948
1949         // check that the message originated elsewhere and is a top-level post
1950
1951         if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1952                 return false;
1953
1954
1955         $u = q("select * from user where uid = %d limit 1",
1956                 intval($uid)
1957         );
1958         if(! count($u))
1959                 return false;
1960
1961         $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1962         $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1963
1964
1965         $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1966
1967         // Diaspora uses their own hardwired link URL in @-tags
1968         // instead of the one we supply with webfinger
1969
1970         $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1971
1972         $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1973         if($cnt) {
1974                 foreach($matches as $mtch) {
1975                         if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1976                                 $mention = true;
1977                                 logger('tgroup_check: mention found: ' . $mtch[2]);
1978                         }
1979                 }
1980         }
1981
1982         if(! $mention)
1983                 return false;
1984
1985         if((! $community_page) && (! $prvgroup))
1986                 return false;
1987
1988
1989
1990         return true;
1991
1992 }
1993
1994
1995
1996
1997
1998
1999 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
2000
2001         $a = get_app();
2002
2003         $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
2004
2005         if($contact['duplex'] && $contact['dfrn-id'])
2006                 $idtosend = '0:' . $orig_id;
2007         if($contact['duplex'] && $contact['issued-id'])
2008                 $idtosend = '1:' . $orig_id;
2009
2010         $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
2011
2012         $rino_enable = get_config('system','rino_encrypt');
2013
2014         if(! $rino_enable)
2015                 $rino = 0;
2016
2017         $ssl_val = intval(get_config('system','ssl_policy'));
2018         $ssl_policy = '';
2019
2020         switch($ssl_val){
2021                 case SSL_POLICY_FULL:
2022                         $ssl_policy = 'full';
2023                         break;
2024                 case SSL_POLICY_SELFSIGN:
2025                         $ssl_policy = 'self';
2026                         break;
2027                 case SSL_POLICY_NONE:
2028                 default:
2029                         $ssl_policy = 'none';
2030                         break;
2031         }
2032
2033         $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
2034
2035         logger('dfrn_deliver: ' . $url);
2036
2037         $xml = fetch_url($url);
2038
2039         $curl_stat = $a->get_curl_code();
2040         if(! $curl_stat)
2041                 return(-1); // timed out
2042
2043         logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
2044
2045         if(! $xml)
2046                 return 3;
2047
2048         if(strpos($xml,'<?xml') === false) {
2049                 logger('dfrn_deliver: no valid XML returned');
2050                 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
2051                 return 3;
2052         }
2053
2054         $res = parse_xml_string($xml);
2055
2056         if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
2057                 return (($res->status) ? $res->status : 3);
2058
2059         $postvars     = array();
2060         $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
2061         $challenge    = hex2bin((string) $res->challenge);
2062         $perm         = (($res->perm) ? $res->perm : null);
2063         $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
2064         $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
2065         $page         = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
2066
2067         if($owner['page-flags'] == PAGE_PRVGROUP)
2068                 $page = 2;
2069
2070         $final_dfrn_id = '';
2071
2072         if($perm) {
2073                 if((($perm == 'rw') && (! intval($contact['writable'])))
2074                 || (($perm == 'r') && (intval($contact['writable'])))) {
2075                         q("update contact set writable = %d where id = %d",
2076                                 intval(($perm == 'rw') ? 1 : 0),
2077                                 intval($contact['id'])
2078                         );
2079                         $contact['writable'] = (string) 1 - intval($contact['writable']);
2080                 }
2081         }
2082
2083         if(($contact['duplex'] && strlen($contact['pubkey']))
2084                 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2085                 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2086                 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
2087                 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
2088         }
2089         else {
2090                 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
2091                 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
2092         }
2093
2094         $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
2095
2096         if(strpos($final_dfrn_id,':') == 1)
2097                 $final_dfrn_id = substr($final_dfrn_id,2);
2098
2099         if($final_dfrn_id != $orig_id) {
2100                 logger('dfrn_deliver: wrong dfrn_id.');
2101                 // did not decode properly - cannot trust this site
2102                 return 3;
2103         }
2104
2105         $postvars['dfrn_id']      = $idtosend;
2106         $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
2107         if($dissolve)
2108                 $postvars['dissolve'] = '1';
2109
2110
2111         if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2112                 $postvars['data'] = $atom;
2113                 $postvars['perm'] = 'rw';
2114         }
2115         else {
2116                 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
2117                 $postvars['perm'] = 'r';
2118         }
2119
2120         $postvars['ssl_policy'] = $ssl_policy;
2121
2122         if($page)
2123                 $postvars['page'] = $page;
2124
2125         if($rino && $rino_allowed && (! $dissolve)) {
2126                 $key = substr(random_string(),0,16);
2127                 $data = bin2hex(aes_encrypt($postvars['data'],$key));
2128                 $postvars['data'] = $data;
2129                 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2130
2131
2132                 if($dfrn_version >= 2.1) {
2133                         if(($contact['duplex'] && strlen($contact['pubkey']))
2134                                 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2135                                 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2136
2137                                 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2138                         }
2139                         else {
2140                                 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2141                         }
2142                 }
2143                 else {
2144                         if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2145                                 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2146                         }
2147                         else {
2148                                 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2149                         }
2150                 }
2151
2152                 logger('md5 rawkey ' . md5($postvars['key']));
2153
2154                 $postvars['key'] = bin2hex($postvars['key']);
2155         }
2156
2157         logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2158
2159         $xml = post_url($contact['notify'],$postvars);
2160
2161         logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2162
2163         $curl_stat = $a->get_curl_code();
2164         if((! $curl_stat) || (! strlen($xml)))
2165                 return(-1); // timed out
2166
2167         if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2168                 return(-1);
2169
2170         if(strpos($xml,'<?xml') === false) {
2171                 logger('dfrn_deliver: phase 2: no valid XML returned');
2172                 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2173                 return 3;
2174         }
2175
2176         if($contact['term-date'] != '0000-00-00 00:00:00') {
2177                 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2178                 require_once('include/Contact.php');
2179                 unmark_for_death($contact);
2180         }
2181
2182         $res = parse_xml_string($xml);
2183
2184         return $res->status;
2185 }
2186
2187
2188 /*
2189   This function returns true if $update has an edited timestamp newer
2190   than $existing, i.e. $update contains new data which should override
2191   what's already there.  If there is no timestamp yet, the update is
2192   assumed to be newer.  If the update has no timestamp, the existing
2193   item is assumed to be up-to-date.  If the timestamps are equal it
2194   assumes the update has been seen before and should be ignored.
2195   */
2196 function edited_timestamp_is_newer($existing, $update) {
2197     if (!x($existing,'edited') || !$existing['edited']) {
2198         return true;
2199     }
2200     if (!x($update,'edited') || !$update['edited']) {
2201         return false;
2202     }
2203     $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2204     $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2205     return (strcmp($existing_edited, $update_edited) < 0);
2206 }
2207
2208 /**
2209  *
2210  * consume_feed - process atom feed and update anything/everything we might need to update
2211  *
2212  * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2213  *
2214  * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2215  *             It is this person's stuff that is going to be updated.
2216  * $contact =  the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2217  *             from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2218  *             have a contact record.
2219  * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2220  *        might not) try and subscribe to it.
2221  * $datedir sorts in reverse order
2222  * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2223  *      imported prior to its children being seen in the stream unless we are certain
2224  *      of how the feed is arranged/ordered.
2225  * With $pass = 1, we only pull parent items out of the stream.
2226  * With $pass = 2, we only pull children (comments/likes).
2227  *
2228  * So running this twice, first with pass 1 and then with pass 2 will do the right
2229  * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2230  * model where comments can have sub-threads. That would require some massive sorting
2231  * to get all the feed items into a mostly linear ordering, and might still require
2232  * recursion.
2233  */
2234
2235 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2236
2237         require_once('library/simplepie/simplepie.inc');
2238         require_once('include/contact_selectors.php');
2239
2240         if(! strlen($xml)) {
2241                 logger('consume_feed: empty input');
2242                 return;
2243         }
2244
2245         // Test - remove before flight
2246 //      if ($contact['network'] === NETWORK_OSTATUS) {
2247 //              $tempfile = tempnam(get_temppath(), "ostatus");
2248 //              file_put_contents($tempfile, $xml);
2249 //      }
2250
2251         $feed = new SimplePie();
2252         $feed->set_raw_data($xml);
2253         if($datedir)
2254                 $feed->enable_order_by_date(true);
2255         else
2256                 $feed->enable_order_by_date(false);
2257         $feed->init();
2258
2259         if($feed->error())
2260                 logger('consume_feed: Error parsing XML: ' . $feed->error());
2261
2262         $permalink = $feed->get_permalink();
2263
2264         // Check at the feed level for updated contact name and/or photo
2265
2266         $name_updated  = '';
2267         $new_name = '';
2268         $photo_timestamp = '';
2269         $photo_url = '';
2270         $birthday = '';
2271         $contact_updated = '';
2272
2273         $hubs = $feed->get_links('hub');
2274         logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2275
2276         if(count($hubs))
2277                 $hub = implode(',', $hubs);
2278
2279         $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2280         if(! $rawtags)
2281                 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2282         if($rawtags) {
2283                 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2284                 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2285                         $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2286                         $new_name = $elems['name'][0]['data'];
2287
2288                         // Manually checking for changed contact names
2289                         if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2290                                 $name_updated = date("c");
2291                                 $photo_timestamp = date("c");
2292                         }
2293                 }
2294                 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2295                         if ($photo_timestamp == "")
2296                                 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2297                         $photo_url = $elems['link'][0]['attribs']['']['href'];
2298                 }
2299
2300                 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2301                         $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2302                 }
2303         }
2304
2305         if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2306                 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2307
2308                 $contact_updated = $photo_timestamp;
2309
2310                 require_once("include/Photo.php");
2311                 $photo_failure = false;
2312                 $have_photo = false;
2313
2314                 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2315                         intval($contact['id']),
2316                         intval($contact['uid'])
2317                 );
2318                 if(count($r)) {
2319                         $resource_id = $r[0]['resource-id'];
2320                         $have_photo = true;
2321                 }
2322                 else {
2323                         $resource_id = photo_new_resource();
2324                 }
2325
2326                 $img_str = fetch_url($photo_url,true);
2327                 // guess mimetype from headers or filename
2328                 $type = guess_image_type($photo_url,true);
2329
2330
2331                 $img = new Photo($img_str, $type);
2332                 if($img->is_valid()) {
2333                         if($have_photo) {
2334                                 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2335                                         dbesc($resource_id),
2336                                         intval($contact['id']),
2337                                         intval($contact['uid'])
2338                                 );
2339                         }
2340
2341                         $img->scaleImageSquare(175);
2342
2343                         $hash = $resource_id;
2344                         $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2345
2346                         $img->scaleImage(80);
2347                         $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2348
2349                         $img->scaleImage(48);
2350                         $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2351
2352                         $a = get_app();
2353
2354                         q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2355                                 WHERE `uid` = %d AND `id` = %d",
2356                                 dbesc(datetime_convert()),
2357                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2358                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2359                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2360                                 intval($contact['uid']),
2361                                 intval($contact['id'])
2362                         );
2363                 }
2364         }
2365
2366         if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2367                 if ($name_updated > $contact_updated)
2368                         $contact_updated = $name_updated;
2369
2370                 $r = q("select * from contact where uid = %d and id = %d limit 1",
2371                         intval($contact['uid']),
2372                         intval($contact['id'])
2373                 );
2374
2375                 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2376                         dbesc(notags(trim($new_name))),
2377                         dbesc(datetime_convert()),
2378                         intval($contact['uid']),
2379                         intval($contact['id'])
2380                 );
2381
2382                 // do our best to update the name on content items
2383
2384                 if(count($r)) {
2385                         q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2386                                 dbesc(notags(trim($new_name))),
2387                                 dbesc($r[0]['name']),
2388                                 dbesc($r[0]['url']),
2389                                 intval($contact['uid'])
2390                         );
2391                 }
2392         }
2393
2394         if ($contact_updated AND $new_name AND $photo_url)
2395                 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2396
2397         if(strlen($birthday)) {
2398                 if(substr($birthday,0,4) != $contact['bdyear']) {
2399                         logger('consume_feed: updating birthday: ' . $birthday);
2400
2401                         /**
2402                          *
2403                          * Add new birthday event for this person
2404                          *
2405                          * $bdtext is just a readable placeholder in case the event is shared
2406                          * with others. We will replace it during presentation to our $importer
2407                          * to contain a sparkle link and perhaps a photo.
2408                          *
2409                          */
2410
2411                         $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2412                         $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2413
2414
2415                         $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2416                                 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2417                                 intval($contact['uid']),
2418                                 intval($contact['id']),
2419                                 dbesc(datetime_convert()),
2420                                 dbesc(datetime_convert()),
2421                                 dbesc(datetime_convert('UTC','UTC', $birthday)),
2422                                 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2423                                 dbesc($bdtext),
2424                                 dbesc($bdtext2),
2425                                 dbesc('birthday')
2426                         );
2427
2428
2429                         // update bdyear
2430
2431                         q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2432                                 dbesc(substr($birthday,0,4)),
2433                                 intval($contact['uid']),
2434                                 intval($contact['id'])
2435                         );
2436
2437                         // This function is called twice without reloading the contact
2438                         // Make sure we only create one event. This is why &$contact
2439                         // is a reference var in this function
2440
2441                         $contact['bdyear'] = substr($birthday,0,4);
2442                 }
2443         }
2444
2445         $community_page = 0;
2446         $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2447         if($rawtags) {
2448                 $community_page = intval($rawtags[0]['data']);
2449         }
2450         if(is_array($contact) && intval($contact['forum']) != $community_page) {
2451                 q("update contact set forum = %d where id = %d",
2452                         intval($community_page),
2453                         intval($contact['id'])
2454                 );
2455                 $contact['forum'] = (string) $community_page;
2456         }
2457
2458
2459         // process any deleted entries
2460
2461         $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2462         if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2463                 foreach($del_entries as $dentry) {
2464                         $deleted = false;
2465                         if(isset($dentry['attribs']['']['ref'])) {
2466                                 $uri = $dentry['attribs']['']['ref'];
2467                                 $deleted = true;
2468                                 if(isset($dentry['attribs']['']['when'])) {
2469                                         $when = $dentry['attribs']['']['when'];
2470                                         $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2471                                 }
2472                                 else
2473                                         $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2474                         }
2475                         if($deleted && is_array($contact)) {
2476                                 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2477                                         WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2478                                         dbesc($uri),
2479                                         intval($importer['uid']),
2480                                         intval($contact['id'])
2481                                 );
2482                                 if(count($r)) {
2483                                         $item = $r[0];
2484
2485                                         if(! $item['deleted'])
2486                                                 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2487
2488                                         if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2489                                                 $xo = parse_xml_string($item['object'],false);
2490                                                 $xt = parse_xml_string($item['target'],false);
2491                                                 if($xt->type === ACTIVITY_OBJ_NOTE) {
2492                                                         $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2493                                                                 dbesc($xt->id),
2494                                                                 intval($importer['importer_uid'])
2495                                                         );
2496                                                         if(count($i)) {
2497
2498                                                                 // For tags, the owner cannot remove the tag on the author's copy of the post.
2499
2500                                                                 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2501                                                                 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2502                                                                 $author_copy = (($item['origin']) ? true : false);
2503
2504                                                                 if($owner_remove && $author_copy)
2505                                                                         continue;
2506                                                                 if($author_remove || $owner_remove) {
2507                                                                         $tags = explode(',',$i[0]['tag']);
2508                                                                         $newtags = array();
2509                                                                         if(count($tags)) {
2510                                                                                 foreach($tags as $tag)
2511                                                                                         if(trim($tag) !== trim($xo->body))
2512                                                                                                 $newtags[] = trim($tag);
2513                                                                         }
2514                                                                         q("update item set tag = '%s' where id = %d",
2515                                                                                 dbesc(implode(',',$newtags)),
2516                                                                                 intval($i[0]['id'])
2517                                                                         );
2518                                                                         create_tags_from_item($i[0]['id']);
2519                                                                 }
2520                                                         }
2521                                                 }
2522                                         }
2523
2524                                         if($item['uri'] == $item['parent-uri']) {
2525                                                 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2526                                                         `body` = '', `title` = ''
2527                                                         WHERE `parent-uri` = '%s' AND `uid` = %d",
2528                                                         dbesc($when),
2529                                                         dbesc(datetime_convert()),
2530                                                         dbesc($item['uri']),
2531                                                         intval($importer['uid'])
2532                                                 );
2533                                                 create_tags_from_itemuri($item['uri'], $importer['uid']);
2534                                                 create_files_from_itemuri($item['uri'], $importer['uid']);
2535                                                 update_thread_uri($item['uri'], $importer['uid']);
2536                                         }
2537                                         else {
2538                                                 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2539                                                         `body` = '', `title` = ''
2540                                                         WHERE `uri` = '%s' AND `uid` = %d",
2541                                                         dbesc($when),
2542                                                         dbesc(datetime_convert()),
2543                                                         dbesc($uri),
2544                                                         intval($importer['uid'])
2545                                                 );
2546                                                 create_tags_from_itemuri($uri, $importer['uid']);
2547                                                 create_files_from_itemuri($uri, $importer['uid']);
2548                                                 if($item['last-child']) {
2549                                                         // ensure that last-child is set in case the comment that had it just got wiped.
2550                                                         q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2551                                                                 dbesc(datetime_convert()),
2552                                                                 dbesc($item['parent-uri']),
2553                                                                 intval($item['uid'])
2554                                                         );
2555                                                         // who is the last child now?
2556                                                         $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2557                                                                 ORDER BY `created` DESC LIMIT 1",
2558                                                                         dbesc($item['parent-uri']),
2559                                                                         intval($importer['uid'])
2560                                                         );
2561                                                         if(count($r)) {
2562                                                                 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2563                                                                         intval($r[0]['id'])
2564                                                                 );
2565                                                         }
2566                                                 }
2567                                         }
2568                                 }
2569                         }
2570                 }
2571         }
2572
2573         // Now process the feed
2574
2575         if($feed->get_item_quantity()) {
2576
2577                 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2578
2579         // in inverse date order
2580                 if ($datedir)
2581                         $items = array_reverse($feed->get_items());
2582                 else
2583                         $items = $feed->get_items();
2584
2585
2586                 foreach($items as $item) {
2587
2588                         $is_reply = false;
2589                         $item_id = $item->get_id();
2590                         $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2591                         if(isset($rawthread[0]['attribs']['']['ref'])) {
2592                                 $is_reply = true;
2593                                 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2594                         }
2595
2596                         if(($is_reply) && is_array($contact)) {
2597
2598                                 if($pass == 1)
2599                                         continue;
2600
2601                                 // not allowed to post
2602
2603                                 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2604                                         continue;
2605
2606
2607                                 // Have we seen it? If not, import it.
2608
2609                                 $item_id  = $item->get_id();
2610                                 $datarray = get_atom_elements($feed, $item, $contact);
2611
2612                                 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2613                                         $datarray['author-name'] = $contact['name'];
2614                                 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2615                                         $datarray['author-link'] = $contact['url'];
2616                                 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2617                                         $datarray['author-avatar'] = $contact['thumb'];
2618
2619                                 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2620                                         logger('consume_feed: no author information! ' . print_r($datarray,true));
2621                                         continue;
2622                                 }
2623
2624                                 $force_parent = false;
2625                                 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2626                                         if($contact['network'] === NETWORK_OSTATUS)
2627                                                 $force_parent = true;
2628                                         if(strlen($datarray['title']))
2629                                                 unset($datarray['title']);
2630                                         $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2631                                                 dbesc(datetime_convert()),
2632                                                 dbesc($parent_uri),
2633                                                 intval($importer['uid'])
2634                                         );
2635                                         $datarray['last-child'] = 1;
2636                                         update_thread_uri($parent_uri, $importer['uid']);
2637                                 }
2638
2639
2640                                 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2641                                         dbesc($item_id),
2642                                         intval($importer['uid'])
2643                                 );
2644
2645                                 // Update content if 'updated' changes
2646
2647                                 if(count($r)) {
2648                                         if (edited_timestamp_is_newer($r[0], $datarray)) {
2649
2650                                                 // do not accept (ignore) an earlier edit than one we currently have.
2651                                                 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2652                                                         continue;
2653
2654                                                 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2655                                                         dbesc($datarray['title']),
2656                                                         dbesc($datarray['body']),
2657                                                         dbesc($datarray['tag']),
2658                                                         dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2659                                                         dbesc(datetime_convert()),
2660                                                         dbesc($item_id),
2661                                                         intval($importer['uid'])
2662                                                 );
2663                                                 create_tags_from_itemuri($item_id, $importer['uid']);
2664                                                 update_thread_uri($item_id, $importer['uid']);
2665                                         }
2666
2667                                         // update last-child if it changes
2668
2669                                         $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2670                                         if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2671                                                 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2672                                                         dbesc(datetime_convert()),
2673                                                         dbesc($parent_uri),
2674                                                         intval($importer['uid'])
2675                                                 );
2676                                                 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s'  WHERE `uri` = '%s' AND `uid` = %d",
2677                                                         intval($allow[0]['data']),
2678                                                         dbesc(datetime_convert()),
2679                                                         dbesc($item_id),
2680                                                         intval($importer['uid'])
2681                                                 );
2682                                                 update_thread_uri($item_id, $importer['uid']);
2683                                         }
2684                                         continue;
2685                                 }
2686
2687
2688                                 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2689                                         // one way feed - no remote comment ability
2690                                         $datarray['last-child'] = 0;
2691                                 }
2692                                 $datarray['parent-uri'] = $parent_uri;
2693                                 $datarray['uid'] = $importer['uid'];
2694                                 $datarray['contact-id'] = $contact['id'];
2695                                 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2696                                         $datarray['type'] = 'activity';
2697                                         $datarray['gravity'] = GRAVITY_LIKE;
2698                                         // only one like or dislike per person
2699                                         // splitted into two queries for performance issues
2700                                         $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`parent-uri` = '%s') limit 1",
2701                                                 intval($datarray['uid']),
2702                                                 intval($datarray['contact-id']),
2703                                                 dbesc($datarray['verb']),
2704                                                 dbesc($parent_uri)
2705                                         );
2706                                         if($r && count($r))
2707                                                 continue;
2708
2709                                         $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`thr-parent` = '%s') limit 1",
2710                                                 intval($datarray['uid']),
2711                                                 intval($datarray['contact-id']),
2712                                                 dbesc($datarray['verb']),
2713                                                 dbesc($parent_uri)
2714                                         );
2715                                         if($r && count($r))
2716                                                 continue;
2717                                 }
2718
2719                                 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2720                                         $xo = parse_xml_string($datarray['object'],false);
2721                                         $xt = parse_xml_string($datarray['target'],false);
2722
2723                                         if($xt->type == ACTIVITY_OBJ_NOTE) {
2724                                                 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2725                                                         dbesc($xt->id),
2726                                                         intval($importer['importer_uid'])
2727                                                 );
2728                                                 if(! count($r))
2729                                                         continue;
2730
2731                                                 // extract tag, if not duplicate, add to parent item
2732                                                 if($xo->id && $xo->content) {
2733                                                         $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2734                                                         if(! (stristr($r[0]['tag'],$newtag))) {
2735                                                                 q("UPDATE item SET tag = '%s' WHERE id = %d",
2736                                                                         dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2737                                                                         intval($r[0]['id'])
2738                                                                 );
2739                                                                 create_tags_from_item($r[0]['id']);
2740                                                         }
2741                                                 }
2742                                         }
2743                                 }
2744
2745                                 $r = item_store($datarray,$force_parent);
2746                                 continue;
2747                         }
2748
2749                         else {
2750
2751                                 // Head post of a conversation. Have we seen it? If not, import it.
2752
2753                                 $item_id  = $item->get_id();
2754
2755                                 $datarray = get_atom_elements($feed, $item, $contact);
2756
2757                                 if(is_array($contact)) {
2758                                         if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2759                                                 $datarray['author-name'] = $contact['name'];
2760                                         if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2761                                                 $datarray['author-link'] = $contact['url'];
2762                                         if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2763                                                 $datarray['author-avatar'] = $contact['thumb'];
2764                                 }
2765
2766                                 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2767                                         logger('consume_feed: no author information! ' . print_r($datarray,true));
2768                                         continue;
2769                                 }
2770
2771                                 // special handling for events
2772
2773                                 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2774                                         $ev = bbtoevent($datarray['body']);
2775                                         if(x($ev,'desc') && x($ev,'start')) {
2776                                                 $ev['uid'] = $importer['uid'];
2777                                                 $ev['uri'] = $item_id;
2778                                                 $ev['edited'] = $datarray['edited'];
2779                                                 $ev['private'] = $datarray['private'];
2780
2781                                                 if(is_array($contact))
2782                                                         $ev['cid'] = $contact['id'];
2783                                                 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2784                                                         dbesc($item_id),
2785                                                         intval($importer['uid'])
2786                                                 );
2787                                                 if(count($r))
2788                                                         $ev['id'] = $r[0]['id'];
2789                                                 $xyz = event_store($ev);
2790                                                 continue;
2791                                         }
2792                                 }
2793
2794                                 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2795                                         if(strlen($datarray['title']))
2796                                                 unset($datarray['title']);
2797                                         $datarray['last-child'] = 1;
2798                                 }
2799
2800
2801                                 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2802                                         dbesc($item_id),
2803                                         intval($importer['uid'])
2804                                 );
2805
2806                                 // Update content if 'updated' changes
2807
2808                                 if(count($r)) {
2809                                         if (edited_timestamp_is_newer($r[0], $datarray)) {
2810
2811                                                 // do not accept (ignore) an earlier edit than one we currently have.
2812                                                 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2813                                                         continue;
2814
2815                                                 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2816                                                         dbesc($datarray['title']),
2817                                                         dbesc($datarray['body']),
2818                                                         dbesc($datarray['tag']),
2819                                                         dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2820                                                         dbesc(datetime_convert()),
2821                                                         dbesc($item_id),
2822                                                         intval($importer['uid'])
2823                                                 );
2824                                                 create_tags_from_itemuri($item_id, $importer['uid']);
2825                                                 update_thread_uri($item_id, $importer['uid']);
2826                                         }
2827
2828                                         // update last-child if it changes
2829
2830                                         $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2831                                         if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2832                                                 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2833                                                         intval($allow[0]['data']),
2834                                                         dbesc(datetime_convert()),
2835                                                         dbesc($item_id),
2836                                                         intval($importer['uid'])
2837                                                 );
2838                                                 update_thread_uri($item_id, $importer['uid']);
2839                                         }
2840                                         continue;
2841                                 }
2842
2843                                 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2844                                         logger('consume-feed: New follower');
2845                                         new_follower($importer,$contact,$datarray,$item);
2846                                         return;
2847                                 }
2848                                 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW))  {
2849                                         lose_follower($importer,$contact,$datarray,$item);
2850                                         return;
2851                                 }
2852
2853                                 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2854                                         logger('consume-feed: New friend request');
2855                                         new_follower($importer,$contact,$datarray,$item,true);
2856                                         return;
2857                                 }
2858                                 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND))  {
2859                                         lose_sharer($importer,$contact,$datarray,$item);
2860                                         return;
2861                                 }
2862
2863
2864                                 if(! is_array($contact))
2865                                         return;
2866
2867
2868                                 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2869                                                 // one way feed - no remote comment ability
2870                                                 $datarray['last-child'] = 0;
2871                                 }
2872                                 if($contact['network'] === NETWORK_FEED)
2873                                         $datarray['private'] = 2;
2874
2875                                 $datarray['parent-uri'] = $item_id;
2876                                 $datarray['uid'] = $importer['uid'];
2877                                 $datarray['contact-id'] = $contact['id'];
2878
2879                                 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2880                                         // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2881                                         // but otherwise there's a possible data mixup on the sender's system.
2882                                         // the tgroup delivery code called from item_store will correct it if it's a forum,
2883                                         // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2884                                         logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2885                                         $datarray['owner-name']   = $contact['name'];
2886                                         $datarray['owner-link']   = $contact['url'];
2887                                         $datarray['owner-avatar'] = $contact['thumb'];
2888                                 }
2889
2890                                 // We've allowed "followers" to reach this point so we can decide if they are
2891                                 // posting an @-tag delivery, which followers are allowed to do for certain
2892                                 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2893
2894                                 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2895                                         continue;
2896
2897                                 // This is my contact on another system, but it's really me.
2898                                 // Turn this into a wall post.
2899                                 $notify = item_is_remote_self($contact, $datarray);
2900
2901                                 $r = item_store($datarray, false, $notify);
2902                                 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2903                                 continue;
2904
2905                         }
2906                 }
2907         }
2908 }
2909
2910 function item_is_remote_self($contact, &$datarray) {
2911         $a = get_app();
2912
2913         if (!$contact['remote_self'])
2914                 return false;
2915
2916         // Prevent the forwarding of posts that are forwarded
2917         if ($datarray["extid"] == NETWORK_DFRN)
2918                 return false;
2919
2920         // Prevent to forward already forwarded posts
2921         if ($datarray["app"] == $a->get_hostname())
2922                 return false;
2923
2924         // Only forward posts
2925         if ($datarray["verb"] != ACTIVITY_POST)
2926                 return false;
2927
2928         if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2929                 return false;
2930
2931         $datarray2 = $datarray;
2932         logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2933         if ($contact['remote_self'] == 2) {
2934                 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2935                         intval($contact['uid']));
2936                 if (count($r)) {
2937                         $datarray['contact-id'] = $r[0]["id"];
2938
2939                         $datarray['owner-name'] = $r[0]["name"];
2940                         $datarray['owner-link'] = $r[0]["url"];
2941                         $datarray['owner-avatar'] = $r[0]["thumb"];
2942
2943                         $datarray['author-name']   = $datarray['owner-name'];
2944                         $datarray['author-link']   = $datarray['owner-link'];
2945                         $datarray['author-avatar'] = $datarray['owner-avatar'];
2946                 }
2947
2948                 if ($contact['network'] != NETWORK_FEED) {
2949                         $datarray["guid"] = get_guid(32);
2950                         unset($datarray["plink"]);
2951                         $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid']);
2952                         $datarray["parent-uri"] = $datarray["uri"];
2953                         $datarray["extid"] = $contact['network'];
2954                         $urlpart = parse_url($datarray2['author-link']);
2955                         $datarray["app"] = $urlpart["host"];
2956                 } else
2957                         $datarray['private'] = 0;
2958         }
2959
2960         //if (!isset($datarray["app"]) OR ($datarray["app"] == ""))
2961         //      $datarray["app"] = network_to_name($contact['network']);
2962
2963         if ($contact['network'] != NETWORK_FEED) {
2964                 // Store the original post
2965                 $r = item_store($datarray2, false, false);
2966                 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2967         } else
2968                 $datarray["app"] = "Feed";
2969
2970         return true;
2971 }
2972
2973 function local_delivery($importer,$data) {
2974         $a = get_app();
2975
2976         logger(__function__, LOGGER_TRACE);
2977
2978         if($importer['readonly']) {
2979                 // We aren't receiving stuff from this person. But we will quietly ignore them
2980                 // rather than a blatant "go away" message.
2981                 logger('local_delivery: ignoring');
2982                 return 0;
2983                 //NOTREACHED
2984         }
2985
2986         // Consume notification feed. This may differ from consuming a public feed in several ways
2987         // - might contain email or friend suggestions
2988         // - might contain remote followup to our message
2989         //              - in which case we need to accept it and then notify other conversants
2990         // - we may need to send various email notifications
2991
2992         $feed = new SimplePie();
2993         $feed->set_raw_data($data);
2994         $feed->enable_order_by_date(false);
2995         $feed->init();
2996
2997
2998         if($feed->error())
2999                 logger('local_delivery: Error parsing XML: ' . $feed->error());
3000
3001
3002         // Check at the feed level for updated contact name and/or photo
3003
3004         $name_updated  = '';
3005         $new_name = '';
3006         $photo_timestamp = '';
3007         $photo_url = '';
3008         $contact_updated = '';
3009
3010
3011         $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
3012
3013 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
3014 //      if(! $rawtags)
3015 //              $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
3016
3017         if($rawtags) {
3018                 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
3019                 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
3020                         $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
3021                         $new_name = $elems['name'][0]['data'];
3022
3023                         // Manually checking for changed contact names
3024                         if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
3025                                 $name_updated = date("c");
3026                                 $photo_timestamp = date("c");
3027                         }
3028                 }
3029                 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
3030                         if ($photo_timestamp == "")
3031                                 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
3032                         $photo_url = $elems['link'][0]['attribs']['']['href'];
3033                 }
3034         }
3035
3036         if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
3037
3038                 $contact_updated = $photo_timestamp;
3039
3040                 logger('local_delivery: Updating photo for ' . $importer['name']);
3041                 require_once("include/Photo.php");
3042                 $photo_failure = false;
3043                 $have_photo = false;
3044
3045                 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
3046                         intval($importer['id']),
3047                         intval($importer['importer_uid'])
3048                 );
3049                 if(count($r)) {
3050                         $resource_id = $r[0]['resource-id'];
3051                         $have_photo = true;
3052                 }
3053                 else {
3054                         $resource_id = photo_new_resource();
3055                 }
3056
3057                 $img_str = fetch_url($photo_url,true);
3058                 // guess mimetype from headers or filename
3059                 $type = guess_image_type($photo_url,true);
3060
3061
3062                 $img = new Photo($img_str, $type);
3063                 if($img->is_valid()) {
3064                         if($have_photo) {
3065                                 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
3066                                         dbesc($resource_id),
3067                                         intval($importer['id']),
3068                                         intval($importer['importer_uid'])
3069                                 );
3070                         }
3071
3072                         $img->scaleImageSquare(175);
3073
3074                         $hash = $resource_id;
3075                         $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
3076
3077                         $img->scaleImage(80);
3078                         $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
3079
3080                         $img->scaleImage(48);
3081                         $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
3082
3083                         $a = get_app();
3084
3085                         q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
3086                                 WHERE `uid` = %d AND `id` = %d",
3087                                 dbesc(datetime_convert()),
3088                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
3089                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
3090                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
3091                                 intval($importer['importer_uid']),
3092                                 intval($importer['id'])
3093                         );
3094                 }
3095         }
3096
3097         if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
3098                 if ($name_updated > $contact_updated)
3099                         $contact_updated = $name_updated;
3100
3101                 $r = q("select * from contact where uid = %d and id = %d limit 1",
3102                         intval($importer['importer_uid']),
3103                         intval($importer['id'])
3104                 );
3105
3106                 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
3107                         dbesc(notags(trim($new_name))),
3108                         dbesc(datetime_convert()),
3109                         intval($importer['importer_uid']),
3110                         intval($importer['id'])
3111                 );
3112
3113                 // do our best to update the name on content items
3114
3115                 if(count($r)) {
3116                         q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
3117                                 dbesc(notags(trim($new_name))),
3118                                 dbesc($r[0]['name']),
3119                                 dbesc($r[0]['url']),
3120                                 intval($importer['importer_uid'])
3121                         );
3122                 }
3123         }
3124
3125         if ($contact_updated AND $new_name AND $photo_url)
3126                 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3127
3128         // Currently unsupported - needs a lot of work
3129         $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3130         if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3131                 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3132                 $newloc = array();
3133                 $newloc['uid'] = $importer['importer_uid'];
3134                 $newloc['cid'] = $importer['id'];
3135                 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3136                 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3137                 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3138                 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3139                 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3140                 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3141                 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3142                 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3143                 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3144                 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3145                 /** relocated user must have original key pair */
3146                 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3147                 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3148
3149                 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3150
3151                 // update contact
3152                 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3153                         intval($importer['id']),
3154                         intval($importer['importer_uid']));
3155                 if ($r === false)
3156                         return 1;
3157                 $old = $r[0];
3158
3159                 $x = q("UPDATE contact SET
3160                                         name = '%s',
3161                                         photo = '%s',
3162                                         thumb = '%s',
3163                                         micro = '%s',
3164                                         url = '%s',
3165                                         nurl = '%s',
3166                                         request = '%s',
3167                                         confirm = '%s',
3168                                         notify = '%s',
3169                                         poll = '%s',
3170                                         `site-pubkey` = '%s'
3171                         WHERE id=%d AND uid=%d;",
3172                                         dbesc($newloc['name']),
3173                                         dbesc($newloc['photo']),
3174                                         dbesc($newloc['thumb']),
3175                                         dbesc($newloc['micro']),
3176                                         dbesc($newloc['url']),
3177                                         dbesc(normalise_link($newloc['url'])),
3178                                         dbesc($newloc['request']),
3179                                         dbesc($newloc['confirm']),
3180                                         dbesc($newloc['notify']),
3181                                         dbesc($newloc['poll']),
3182                                         dbesc($newloc['sitepubkey']),
3183                                         intval($importer['id']),
3184                                         intval($importer['importer_uid']));
3185
3186                 if ($x === false)
3187                         return 1;
3188                 // update items
3189                 $fields = array(
3190                         'owner-link' => array($old['url'], $newloc['url']),
3191                         'author-link' => array($old['url'], $newloc['url']),
3192                         'owner-avatar' => array($old['photo'], $newloc['photo']),
3193                         'author-avatar' => array($old['photo'], $newloc['photo']),
3194                         );
3195                 foreach ($fields as $n=>$f){
3196                         $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3197                                         $n, dbesc($f[1]),
3198                                         $n, dbesc($f[0]),
3199                                         intval($importer['importer_uid']));
3200                                 if ($x === false)
3201                                         return 1;
3202                         }
3203
3204                 // TODO
3205                 // merge with current record, current contents have priority
3206                 // update record, set url-updated
3207                 // update profile photos
3208                 // schedule a scan?
3209                 return 0;
3210         }
3211
3212
3213         // handle friend suggestion notification
3214
3215         $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3216         if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3217                 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3218                 $fsugg = array();
3219                 $fsugg['uid'] = $importer['importer_uid'];
3220                 $fsugg['cid'] = $importer['id'];
3221                 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3222                 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3223                 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3224                 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3225                 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3226
3227                 // Does our member already have a friend matching this description?
3228
3229                 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3230                         dbesc($fsugg['name']),
3231                         dbesc(normalise_link($fsugg['url'])),
3232                         intval($fsugg['uid'])
3233                 );
3234                 if(count($r))
3235                         return 0;
3236
3237                 // Do we already have an fcontact record for this person?
3238
3239                 $fid = 0;
3240                 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3241                         dbesc($fsugg['url']),
3242                         dbesc($fsugg['name']),
3243                         dbesc($fsugg['request'])
3244                 );
3245                 if(count($r)) {
3246                         $fid = $r[0]['id'];
3247
3248                         // OK, we do. Do we already have an introduction for this person ?
3249                         $r = q("select id from intro where uid = %d and fid = %d limit 1",
3250                                 intval($fsugg['uid']),
3251                                 intval($fid)
3252                         );
3253                         if(count($r))
3254                                 return 0;
3255                 }
3256                 if(! $fid)
3257                         $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3258                         dbesc($fsugg['name']),
3259                         dbesc($fsugg['url']),
3260                         dbesc($fsugg['photo']),
3261                         dbesc($fsugg['request'])
3262                 );
3263                 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3264                         dbesc($fsugg['url']),
3265                         dbesc($fsugg['name']),
3266                         dbesc($fsugg['request'])
3267                 );
3268                 if(count($r)) {
3269                         $fid = $r[0]['id'];
3270                 }
3271                 // database record did not get created. Quietly give up.
3272                 else
3273                         return 0;
3274
3275
3276                 $hash = random_string();
3277
3278                 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3279                         VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3280                         intval($fsugg['uid']),
3281                         intval($fid),
3282                         intval($fsugg['cid']),
3283                         dbesc($fsugg['body']),
3284                         dbesc($hash),
3285                         dbesc(datetime_convert()),
3286                         intval(0)
3287                 );
3288
3289                 notification(array(
3290                         'type'         => NOTIFY_SUGGEST,
3291                         'notify_flags' => $importer['notify-flags'],
3292                         'language'     => $importer['language'],
3293                         'to_name'      => $importer['username'],
3294                         'to_email'     => $importer['email'],
3295                         'uid'          => $importer['importer_uid'],
3296                         'item'         => $fsugg,
3297                         'link'         => $a->get_baseurl() . '/notifications/intros',
3298                         'source_name'  => $importer['name'],
3299                         'source_link'  => $importer['url'],
3300                         'source_photo' => $importer['photo'],
3301                         'verb'         => ACTIVITY_REQ_FRIEND,
3302                         'otype'        => 'intro'
3303                 ));
3304
3305                 return 0;
3306         }
3307
3308         $ismail = false;
3309
3310         $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3311         if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3312
3313                 logger('local_delivery: private message received');
3314
3315                 $ismail = true;
3316                 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3317
3318                 $msg = array();
3319                 $msg['uid'] = $importer['importer_uid'];
3320                 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3321                 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3322                 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3323                 $msg['contact-id'] = $importer['id'];
3324                 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3325                 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3326                 $msg['seen'] = 0;
3327                 $msg['replied'] = 0;
3328                 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3329                 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3330                 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3331
3332                 dbesc_array($msg);
3333
3334                 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3335                         . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3336
3337                 // send notifications.
3338
3339                 require_once('include/enotify.php');
3340
3341                 $notif_params = array(
3342                         'type' => NOTIFY_MAIL,
3343                         'notify_flags' => $importer['notify-flags'],
3344                         'language' => $importer['language'],
3345                         'to_name' => $importer['username'],
3346                         'to_email' => $importer['email'],
3347                         'uid' => $importer['importer_uid'],
3348                         'item' => $msg,
3349                         'source_name' => $msg['from-name'],
3350                         'source_link' => $importer['url'],
3351                         'source_photo' => $importer['thumb'],
3352                         'verb' => ACTIVITY_POST,
3353                         'otype' => 'mail'
3354                 );
3355
3356                 notification($notif_params);
3357                 return 0;
3358
3359                 // NOTREACHED
3360         }
3361
3362         $community_page = 0;
3363         $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3364         if($rawtags) {
3365                 $community_page = intval($rawtags[0]['data']);
3366         }
3367         if(intval($importer['forum']) != $community_page) {
3368                 q("update contact set forum = %d where id = %d",
3369                         intval($community_page),
3370                         intval($importer['id'])
3371                 );
3372                 $importer['forum'] = (string) $community_page;
3373         }
3374
3375         logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3376
3377         // process any deleted entries
3378
3379         $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3380         if(is_array($del_entries) && count($del_entries)) {
3381                 foreach($del_entries as $dentry) {
3382                         $deleted = false;
3383                         if(isset($dentry['attribs']['']['ref'])) {
3384                                 $uri = $dentry['attribs']['']['ref'];
3385                                 $deleted = true;
3386                                 if(isset($dentry['attribs']['']['when'])) {
3387                                         $when = $dentry['attribs']['']['when'];
3388                                         $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3389                                 }
3390                                 else
3391                                         $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3392                         }
3393                         if($deleted) {
3394
3395                                 // check for relayed deletes to our conversation
3396
3397                                 $is_reply = false;
3398                                 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3399                                         dbesc($uri),
3400                                         intval($importer['importer_uid'])
3401                                 );
3402                                 if(count($r)) {
3403                                         $parent_uri = $r[0]['parent-uri'];
3404                                         if($r[0]['id'] != $r[0]['parent'])
3405                                                 $is_reply = true;
3406                                 }
3407
3408                                 if($is_reply) {
3409                                         $community = false;
3410
3411                                         if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3412                                                 $sql_extra = '';
3413                                                 $community = true;
3414                                                 logger('local_delivery: possible community delete');
3415                                         }
3416                                         else
3417                                                 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3418
3419                                         // was the top-level post for this reply written by somebody on this site?
3420                                         // Specifically, the recipient?
3421
3422                                         $is_a_remote_delete = false;
3423
3424                                         // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3425                                         $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3426                                                 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3427                                                 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3428                                                 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3429                                                 AND `item`.`uid` = %d
3430                                                 $sql_extra
3431                                                 LIMIT 1",
3432                                                 dbesc($parent_uri),
3433                                                 dbesc($parent_uri),
3434                                                 dbesc($parent_uri),
3435                                                 intval($importer['importer_uid'])
3436                                         );
3437                                         if($r && count($r))
3438                                                 $is_a_remote_delete = true;
3439
3440                                         // Does this have the characteristics of a community or private group comment?
3441                                         // If it's a reply to a wall post on a community/prvgroup page it's a
3442                                         // valid community comment. Also forum_mode makes it valid for sure.
3443                                         // If neither, it's not.
3444
3445                                         if($is_a_remote_delete && $community) {
3446                                                 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3447                                                         $is_a_remote_delete = false;
3448                                                         logger('local_delivery: not a community delete');
3449                                                 }
3450                                         }
3451
3452                                         if($is_a_remote_delete) {
3453                                                 logger('local_delivery: received remote delete');
3454                                         }
3455                                 }
3456
3457                                 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3458                                         WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3459                                         dbesc($uri),
3460                                         intval($importer['importer_uid']),
3461                                         intval($importer['id'])
3462                                 );
3463
3464                                 if(count($r)) {
3465                                         $item = $r[0];
3466
3467                                         if($item['deleted'])
3468                                                 continue;
3469
3470                                         logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3471
3472                                         if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3473                                                 $xo = parse_xml_string($item['object'],false);
3474                                                 $xt = parse_xml_string($item['target'],false);
3475
3476                                                 if($xt->type === ACTIVITY_OBJ_NOTE) {
3477                                                         $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3478                                                                 dbesc($xt->id),
3479                                                                 intval($importer['importer_uid'])
3480                                                         );
3481                                                         if(count($i)) {
3482
3483                                                                 // For tags, the owner cannot remove the tag on the author's copy of the post.
3484
3485                                                                 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3486                                                                 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3487                                                                 $author_copy = (($item['origin']) ? true : false);
3488
3489                                                                 if($owner_remove && $author_copy)
3490                                                                         continue;
3491                                                                 if($author_remove || $owner_remove) {
3492                                                                         $tags = explode(',',$i[0]['tag']);
3493                                                                         $newtags = array();
3494                                                                         if(count($tags)) {
3495                                                                                 foreach($tags as $tag)
3496                                                                                         if(trim($tag) !== trim($xo->body))
3497                                                                                                 $newtags[] = trim($tag);
3498                                                                         }
3499                                                                         q("update item set tag = '%s' where id = %d",
3500                                                                                 dbesc(implode(',',$newtags)),
3501                                                                                 intval($i[0]['id'])
3502                                                                         );
3503                                                                         create_tags_from_item($i[0]['id']);
3504                                                                 }
3505                                                         }
3506                                                 }
3507                                         }
3508
3509                                         if($item['uri'] == $item['parent-uri']) {
3510                                                 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3511                                                         `body` = '', `title` = ''
3512                                                         WHERE `parent-uri` = '%s' AND `uid` = %d",
3513                                                         dbesc($when),
3514                                                         dbesc(datetime_convert()),
3515                                                         dbesc($item['uri']),
3516                                                         intval($importer['importer_uid'])
3517                                                 );
3518                                                 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3519                                                 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3520                                                 update_thread_uri($item['uri'], $importer['importer_uid']);
3521                                         }
3522                                         else {
3523                                                 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3524                                                         `body` = '', `title` = ''
3525                                                         WHERE `uri` = '%s' AND `uid` = %d",
3526                                                         dbesc($when),
3527                                                         dbesc(datetime_convert()),
3528                                                         dbesc($uri),
3529                                                         intval($importer['importer_uid'])
3530                                                 );
3531                                                 create_tags_from_itemuri($uri, $importer['importer_uid']);
3532                                                 create_files_from_itemuri($uri, $importer['importer_uid']);
3533                                                 update_thread_uri($uri, $importer['importer_uid']);
3534                                                 if($item['last-child']) {
3535                                                         // ensure that last-child is set in case the comment that had it just got wiped.
3536                                                         q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3537                                                                 dbesc(datetime_convert()),
3538                                                                 dbesc($item['parent-uri']),
3539                                                                 intval($item['uid'])
3540                                                         );
3541                                                         // who is the last child now?
3542                                                         $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3543                                                                 ORDER BY `created` DESC LIMIT 1",
3544                                                                         dbesc($item['parent-uri']),
3545                                                                         intval($importer['importer_uid'])
3546                                                         );
3547                                                         if(count($r)) {
3548                                                                 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3549                                                                         intval($r[0]['id'])
3550                                                                 );
3551                                                         }
3552                                                 }
3553                                                 // if this is a relayed delete, propagate it to other recipients
3554
3555                                                 if($is_a_remote_delete)
3556                                                         proc_run('php',"include/notifier.php","drop",$item['id']);
3557                                         }
3558                                 }
3559                         }
3560                 }
3561         }
3562
3563
3564         foreach($feed->get_items() as $item) {
3565
3566                 $is_reply = false;
3567                 $item_id = $item->get_id();
3568                 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3569                 if(isset($rawthread[0]['attribs']['']['ref'])) {
3570                         $is_reply = true;
3571                         $parent_uri = $rawthread[0]['attribs']['']['ref'];
3572                 }
3573
3574                 if($is_reply) {
3575                         $community = false;
3576
3577                         if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3578                                 $sql_extra = '';
3579                                 $community = true;
3580                                 logger('local_delivery: possible community reply');
3581                         }
3582                         else
3583                                 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3584
3585                         // was the top-level post for this reply written by somebody on this site?
3586                         // Specifically, the recipient?
3587
3588                         $is_a_remote_comment = false;
3589                         $top_uri = $parent_uri;
3590
3591                         $r = q("select `item`.`parent-uri` from `item`
3592                                 WHERE `item`.`uri` = '%s'
3593                                 LIMIT 1",
3594                                 dbesc($parent_uri)
3595                         );
3596                         if($r && count($r)) {
3597                                 $top_uri = $r[0]['parent-uri'];
3598
3599                                 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3600                                 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3601                                         `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3602                                         INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3603                                         WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3604                                         AND `item`.`uid` = %d
3605                                         $sql_extra
3606                                         LIMIT 1",
3607                                         dbesc($top_uri),
3608                                         dbesc($top_uri),
3609                                         dbesc($top_uri),
3610                                         intval($importer['importer_uid'])
3611                                 );
3612                                 if($r && count($r))
3613                                         $is_a_remote_comment = true;
3614                         }
3615
3616                         // Does this have the characteristics of a community or private group comment?
3617                         // If it's a reply to a wall post on a community/prvgroup page it's a
3618                         // valid community comment. Also forum_mode makes it valid for sure.
3619                         // If neither, it's not.
3620
3621                         if($is_a_remote_comment && $community) {
3622                                 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3623                                         $is_a_remote_comment = false;
3624                                         logger('local_delivery: not a community reply');
3625                                 }
3626                         }
3627
3628                         if($is_a_remote_comment) {
3629                                 logger('local_delivery: received remote comment');
3630                                 $is_like = false;
3631                                 // remote reply to our post. Import and then notify everybody else.
3632
3633                                 $datarray = get_atom_elements($feed, $item);
3634
3635                                 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body`  FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3636                                         dbesc($item_id),
3637                                         intval($importer['importer_uid'])
3638                                 );
3639
3640                                 // Update content if 'updated' changes
3641
3642                                 if(count($r)) {
3643                                         $iid = $r[0]['id'];
3644                                         if (edited_timestamp_is_newer($r[0], $datarray)) {
3645
3646                                                 // do not accept (ignore) an earlier edit than one we currently have.
3647                                                 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3648                                                         continue;
3649
3650                                                 logger('received updated comment' , LOGGER_DEBUG);
3651                                                 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3652                                                         dbesc($datarray['title']),
3653                                                         dbesc($datarray['body']),
3654                                                         dbesc($datarray['tag']),
3655                                                         dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3656                                                         dbesc(datetime_convert()),
3657                                                         dbesc($item_id),
3658                                                         intval($importer['importer_uid'])
3659                                                 );
3660                                                 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3661
3662                                                 proc_run('php',"include/notifier.php","comment-import",$iid);
3663
3664                                         }
3665
3666                                         continue;
3667                                 }
3668
3669
3670
3671                                 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3672                                         intval($importer['importer_uid'])
3673                                 );
3674
3675
3676                                 $datarray['type'] = 'remote-comment';
3677                                 $datarray['wall'] = 1;
3678                                 $datarray['parent-uri'] = $parent_uri;
3679                                 $datarray['uid'] = $importer['importer_uid'];
3680                                 $datarray['owner-name'] = $own[0]['name'];
3681                                 $datarray['owner-link'] = $own[0]['url'];
3682                                 $datarray['owner-avatar'] = $own[0]['thumb'];
3683                                 $datarray['contact-id'] = $importer['id'];
3684
3685                                 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3686                                         $is_like = true;
3687                                         $datarray['type'] = 'activity';
3688                                         $datarray['gravity'] = GRAVITY_LIKE;
3689                                         $datarray['last-child'] = 0;
3690                                         // only one like or dislike per person
3691                                         // splitted into two queries for performance issues
3692                                         $r = q("select id from item where uid = %d and `contact-id` = %d and verb = '%s' and (`parent-uri` = '%s') and deleted = 0 limit 1",
3693                                                 intval($datarray['uid']),
3694                                                 intval($datarray['contact-id']),
3695                                                 dbesc($datarray['verb']),
3696                                                 dbesc($datarray['parent-uri'])
3697
3698                                         );
3699                                         if($r && count($r))
3700                                                 continue;
3701
3702                                         $r = q("select id from item where uid = %d and `contact-id` = %d and verb = '%s' and (`thr-parent` = '%s') and deleted = 0 limit 1",
3703                                                 intval($datarray['uid']),
3704                                                 intval($datarray['contact-id']),
3705                                                 dbesc($datarray['verb']),
3706                                                 dbesc($datarray['parent-uri'])
3707
3708                                         );
3709                                         if($r && count($r))
3710                                                 continue;
3711                                 }
3712
3713                                 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3714
3715                                         $xo = parse_xml_string($datarray['object'],false);
3716                                         $xt = parse_xml_string($datarray['target'],false);
3717
3718                                         if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3719
3720                                                 // fetch the parent item
3721
3722                                                 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3723                                                         dbesc($xt->id),
3724                                                         intval($importer['importer_uid'])
3725                                                 );
3726                                                 if(! count($tagp))
3727                                                         continue;
3728
3729                                                 // extract tag, if not duplicate, and this user allows tags, add to parent item
3730
3731                                                 if($xo->id && $xo->content) {
3732                                                         $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3733                                                         if(! (stristr($tagp[0]['tag'],$newtag))) {
3734                                                                 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3735                                                                         intval($importer['importer_uid'])
3736                                                                 );
3737                                                                 if(count($i) && ! intval($i[0]['blocktags'])) {
3738                                                                         q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3739                                                                                 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3740                                                                                 intval($tagp[0]['id']),
3741                                                                                 dbesc(datetime_convert()),
3742                                                                                 dbesc(datetime_convert())
3743                                                                         );
3744                                                                         create_tags_from_item($tagp[0]['id']);
3745                                                                 }
3746                                                         }
3747                                                 }
3748                                         }
3749                                 }
3750
3751
3752                                 $posted_id = item_store($datarray);
3753                                 $parent = 0;
3754
3755                                 if($posted_id) {
3756
3757                                         $datarray["id"] = $posted_id;
3758
3759                                         $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3760                                                 intval($posted_id),
3761                                                 intval($importer['importer_uid'])
3762                                         );
3763                                         if(count($r)) {
3764                                                 $parent = $r[0]['parent'];
3765                                                 $parent_uri = $r[0]['parent-uri'];
3766                                         }
3767
3768                                         if(! $is_like) {
3769                                                 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3770                                                         dbesc(datetime_convert()),
3771                                                         intval($importer['importer_uid']),
3772                                                         intval($r[0]['parent'])
3773                                                 );
3774
3775                                                 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3776                                                         dbesc(datetime_convert()),
3777                                                         intval($importer['importer_uid']),
3778                                                         intval($posted_id)
3779                                                 );
3780                                         }
3781
3782                                         if($posted_id && $parent) {
3783
3784                                                 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3785
3786                                                 if((! $is_like) && (! $importer['self'])) {
3787
3788                                                         require_once('include/enotify.php');
3789
3790                                                         notification(array(
3791                                                                 'type'         => NOTIFY_COMMENT,
3792                                                                 'notify_flags' => $importer['notify-flags'],
3793                                                                 'language'     => $importer['language'],
3794                                                                 'to_name'      => $importer['username'],
3795                                                                 'to_email'     => $importer['email'],
3796                                                                 'uid'          => $importer['importer_uid'],
3797                                                                 'item'         => $datarray,
3798                                                                 'link'             => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3799                                                                 'source_name'  => stripslashes($datarray['author-name']),
3800                                                                 'source_link'  => $datarray['author-link'],
3801                                                                 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3802                                                                         ? $importer['thumb'] : $datarray['author-avatar']),
3803                                                                 'verb'         => ACTIVITY_POST,
3804                                                                 'otype'        => 'item',
3805                                                                 'parent'       => $parent,
3806                                                                 'parent_uri'   => $parent_uri,
3807                                                         ));
3808
3809                                                 }
3810                                         }
3811
3812                                         return 0;
3813                                         // NOTREACHED
3814                                 }
3815                         }
3816                         else {
3817
3818                                 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3819
3820                                 $item_id  = $item->get_id();
3821                                 $datarray = get_atom_elements($feed,$item);
3822
3823                                 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3824                                         continue;
3825
3826                                 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3827                                         dbesc($item_id),
3828                                         intval($importer['importer_uid'])
3829                                 );
3830
3831                                 // Update content if 'updated' changes
3832
3833                                 if(count($r)) {
3834                                         if (edited_timestamp_is_newer($r[0], $datarray)) {
3835
3836                                                 // do not accept (ignore) an earlier edit than one we currently have.
3837                                                 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3838                                                         continue;
3839
3840                                                 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3841                                                         dbesc($datarray['title']),
3842                                                         dbesc($datarray['body']),
3843                                                         dbesc($datarray['tag']),
3844                                                         dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3845                                                         dbesc(datetime_convert()),
3846                                                         dbesc($item_id),
3847                                                         intval($importer['importer_uid'])
3848                                                 );
3849                                                 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3850                                         }
3851
3852                                         // update last-child if it changes
3853
3854                                         $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3855                                         if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3856                                                 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3857                                                         dbesc(datetime_convert()),
3858                                                         dbesc($parent_uri),
3859                                                         intval($importer['importer_uid'])
3860                                                 );
3861                                                 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s'  WHERE `uri` = '%s' AND `uid` = %d",
3862                                                         intval($allow[0]['data']),
3863                                                         dbesc(datetime_convert()),
3864                                                         dbesc($item_id),
3865                                                         intval($importer['importer_uid'])
3866                                                 );
3867                                         }
3868                                         continue;
3869                                 }
3870
3871                                 $datarray['parent-uri'] = $parent_uri;
3872                                 $datarray['uid'] = $importer['importer_uid'];
3873                                 $datarray['contact-id'] = $importer['id'];
3874                                 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3875                                         $datarray['type'] = 'activity';
3876                                         $datarray['gravity'] = GRAVITY_LIKE;
3877                                         // only one like or dislike per person
3878                                         // splitted into two queries for performance issues
3879                                         $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`parent-uri` = '%s') limit 1",
3880                                                 intval($datarray['uid']),
3881                                                 intval($datarray['contact-id']),
3882                                                 dbesc($datarray['verb']),
3883                                                 dbesc($parent_uri)
3884                                         );
3885                                         if($r && count($r))
3886                                                 continue;
3887
3888                                         $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`thr-parent` = '%s') limit 1",
3889                                                 intval($datarray['uid']),
3890                                                 intval($datarray['contact-id']),
3891                                                 dbesc($datarray['verb']),
3892                                                 dbesc($parent_uri)
3893                                         );
3894                                         if($r && count($r))
3895                                                 continue;
3896
3897                                 }
3898
3899                                 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3900
3901                                         $xo = parse_xml_string($datarray['object'],false);
3902                                         $xt = parse_xml_string($datarray['target'],false);
3903
3904                                         if($xt->type == ACTIVITY_OBJ_NOTE) {
3905                                                 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3906                                                         dbesc($xt->id),
3907                                                         intval($importer['importer_uid'])
3908                                                 );
3909                                                 if(! count($r))
3910                                                         continue;
3911
3912                                                 // extract tag, if not duplicate, add to parent item
3913                                                 if($xo->content) {
3914                                                         if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3915                                                                 q("UPDATE item SET tag = '%s' WHERE id = %d",
3916                                                                         dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3917                                                                         intval($r[0]['id'])
3918                                                                 );
3919                                                                 create_tags_from_item($r[0]['id']);
3920                                                         }
3921                                                 }
3922                                         }
3923                                 }
3924
3925                                 $posted_id = item_store($datarray);
3926
3927                                 // find out if our user is involved in this conversation and wants to be notified.
3928
3929                                 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3930
3931                                         $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3932                                                 dbesc($top_uri),
3933                                                 intval($importer['importer_uid'])
3934                                         );
3935
3936                                         if(count($myconv)) {
3937                                                 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3938
3939                                                 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3940                                                 if(! link_compare($datarray['author-link'],$importer_url)) {
3941
3942
3943                                                         foreach($myconv as $conv) {
3944
3945                                                                 // now if we find a match, it means we're in this conversation
3946
3947                                                                 if(! link_compare($conv['author-link'],$importer_url))
3948                                                                         continue;
3949
3950                                                                 require_once('include/enotify.php');
3951
3952                                                                 $conv_parent = $conv['parent'];
3953
3954                                                                 notification(array(
3955                                                                         'type'         => NOTIFY_COMMENT,
3956                                                                         'notify_flags' => $importer['notify-flags'],
3957                                                                         'language'     => $importer['language'],
3958                                                                         'to_name'      => $importer['username'],
3959                                                                         'to_email'     => $importer['email'],
3960                                                                         'uid'          => $importer['importer_uid'],
3961                                                                         'item'         => $datarray,
3962                                                                         'link'             => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3963                                                                         'source_name'  => stripslashes($datarray['author-name']),
3964                                                                         'source_link'  => $datarray['author-link'],
3965                                                                         'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3966                                                                                 ? $importer['thumb'] : $datarray['author-avatar']),
3967                                                                         'verb'         => ACTIVITY_POST,
3968                                                                         'otype'        => 'item',
3969                                                                         'parent'       => $conv_parent,
3970                                                                         'parent_uri'   => $parent_uri
3971
3972                                                                 ));
3973
3974                                                                 // only send one notification
3975                                                                 break;
3976                                                         }
3977                                                 }
3978                                         }
3979                                 }
3980                                 continue;
3981                         }
3982                 }
3983
3984                 else {
3985
3986                         // Head post of a conversation. Have we seen it? If not, import it.
3987
3988
3989                         $item_id  = $item->get_id();
3990                         $datarray = get_atom_elements($feed,$item);
3991
3992                         if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3993                                 $ev = bbtoevent($datarray['body']);
3994                                 if(x($ev,'desc') && x($ev,'start')) {
3995                                         $ev['cid'] = $importer['id'];
3996                                         $ev['uid'] = $importer['uid'];
3997                                         $ev['uri'] = $item_id;
3998                                         $ev['edited'] = $datarray['edited'];
3999                                         $ev['private'] = $datarray['private'];
4000
4001                                         $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4002                                                 dbesc($item_id),
4003                                                 intval($importer['uid'])
4004                                         );
4005                                         if(count($r))
4006                                                 $ev['id'] = $r[0]['id'];
4007                                         $xyz = event_store($ev);
4008                                         continue;
4009                                 }
4010                         }
4011
4012                         $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4013                                 dbesc($item_id),
4014                                 intval($importer['importer_uid'])
4015                         );
4016
4017                         // Update content if 'updated' changes
4018
4019                         if(count($r)) {
4020                                 if (edited_timestamp_is_newer($r[0], $datarray)) {
4021
4022                                         // do not accept (ignore) an earlier edit than one we currently have.
4023                                         if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
4024                                                 continue;
4025
4026                                         $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4027                                                 dbesc($datarray['title']),
4028                                                 dbesc($datarray['body']),
4029                                                 dbesc($datarray['tag']),
4030                                                 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
4031                                                 dbesc(datetime_convert()),
4032                                                 dbesc($item_id),
4033                                                 intval($importer['importer_uid'])
4034                                         );
4035                                         create_tags_from_itemuri($item_id, $importer['importer_uid']);
4036                                         update_thread_uri($item_id, $importer['importer_uid']);
4037                                 }
4038
4039                                 // update last-child if it changes
4040
4041                                 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
4042                                 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
4043                                         $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4044                                                 intval($allow[0]['data']),
4045                                                 dbesc(datetime_convert()),
4046                                                 dbesc($item_id),
4047                                                 intval($importer['importer_uid'])
4048                                         );
4049                                 }
4050                                 continue;
4051                         }
4052
4053                         $datarray['parent-uri'] = $item_id;
4054                         $datarray['uid'] = $importer['importer_uid'];
4055                         $datarray['contact-id'] = $importer['id'];
4056
4057
4058                         if(! link_compare($datarray['owner-link'],$importer['url'])) {
4059                                 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
4060                                 // but otherwise there's a possible data mixup on the sender's system.
4061                                 // the tgroup delivery code called from item_store will correct it if it's a forum,
4062                                 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
4063                                 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
4064                                 $datarray['owner-name']   = $importer['senderName'];
4065                                 $datarray['owner-link']   = $importer['url'];
4066                                 $datarray['owner-avatar'] = $importer['thumb'];
4067                         }
4068
4069                         if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
4070                                 continue;
4071
4072                         // This is my contact on another system, but it's really me.
4073                         // Turn this into a wall post.
4074                         $notify = item_is_remote_self($importer, $datarray);
4075
4076                         $posted_id = item_store($datarray, false, $notify);
4077
4078                         if(stristr($datarray['verb'],ACTIVITY_POKE)) {
4079                                 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
4080                                 if(! $verb)
4081                                         continue;
4082                                 $xo = parse_xml_string($datarray['object'],false);
4083
4084                                 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
4085
4086                                         // somebody was poked/prodded. Was it me?
4087
4088                                         $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
4089
4090                                 foreach($links->link as $l) {
4091                                 $atts = $l->attributes();
4092                                 switch($atts['rel']) {
4093                                         case "alternate":
4094                                                                 $Blink = $atts['href'];
4095                                                                 break;
4096                                                         default:
4097                                                                 break;
4098                                     }
4099                                 }
4100                                         if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
4101
4102                                                 // send a notification
4103                                                 require_once('include/enotify.php');
4104
4105                                                 notification(array(
4106                                                         'type'         => NOTIFY_POKE,
4107                                                         'notify_flags' => $importer['notify-flags'],
4108                                                         'language'     => $importer['language'],
4109                                                         'to_name'      => $importer['username'],
4110                                                         'to_email'     => $importer['email'],
4111                                                         'uid'          => $importer['importer_uid'],
4112                                                         'item'         => $datarray,
4113                                                         'link'             => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4114                                                         'source_name'  => stripslashes($datarray['author-name']),
4115                                                         'source_link'  => $datarray['author-link'],
4116                                                         'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4117                                                                 ? $importer['thumb'] : $datarray['author-avatar']),
4118                                                         'verb'         => $datarray['verb'],
4119                                                         'otype'        => 'person',
4120                                                         'activity'     => $verb,
4121                                                         'parent'       => $datarray['parent']
4122                                                 ));
4123                                         }
4124                                 }
4125                         }
4126
4127                         continue;
4128                 }
4129         }
4130
4131         return 0;
4132         // NOTREACHED
4133
4134 }
4135
4136
4137 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4138         $url = notags(trim($datarray['author-link']));
4139         $name = notags(trim($datarray['author-name']));
4140         $photo = notags(trim($datarray['author-avatar']));
4141
4142         $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4143         if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4144                 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4145
4146         if(is_array($contact)) {
4147                 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4148                         || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4149                         $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4150                                 intval(CONTACT_IS_FRIEND),
4151                                 intval($contact['id']),
4152                                 intval($importer['uid'])
4153                         );
4154                 }
4155                 // send email notification to owner?
4156         }
4157         else {
4158
4159                 // create contact record
4160
4161                 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4162                         `blocked`, `readonly`, `pending`, `writable` )
4163                         VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4164                         intval($importer['uid']),
4165                         dbesc(datetime_convert()),
4166                         dbesc($url),
4167                         dbesc(normalise_link($url)),
4168                         dbesc($name),
4169                         dbesc($nick),
4170                         dbesc($photo),
4171                         dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4172                         intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4173                 );
4174                 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4175                                 intval($importer['uid']),
4176                                 dbesc($url)
4177                 );
4178                 if(count($r))
4179                                 $contact_record = $r[0];
4180
4181                 // create notification
4182                 $hash = random_string();
4183
4184                 if(is_array($contact_record)) {
4185                         $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4186                                 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4187                                 intval($importer['uid']),
4188                                 intval($contact_record['id']),
4189                                 dbesc($hash),
4190                                 dbesc(datetime_convert())
4191                         );
4192                 }
4193
4194                 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4195                         intval($importer['uid'])
4196                 );
4197                 $a = get_app();
4198                 if(count($r)) {
4199
4200                         if(intval($r[0]['def_gid'])) {
4201                                 require_once('include/group.php');
4202                                 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4203                         }
4204
4205                         if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4206                                 in_array($r[0]['page-flags'], array(PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE))) {
4207
4208                                 notification(array(
4209                                         'type'         => NOTIFY_INTRO,
4210                                         'notify_flags' => $r[0]['notify-flags'],
4211                                         'language'     => $r[0]['language'],
4212                                         'to_name'      => $r[0]['username'],
4213                                         'to_email'     => $r[0]['email'],
4214                                         'uid'          => $r[0]['uid'],
4215                                         'link'             => $a->get_baseurl() . '/notifications/intro',
4216                                         'source_name'  => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4217                                         'source_link'  => $contact_record['url'],
4218                                         'source_photo' => $contact_record['photo'],
4219                                         'verb'         => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4220                                         'otype'        => 'intro'
4221                                 ));
4222
4223                         }
4224                 }
4225         }
4226 }
4227
4228 function lose_follower($importer,$contact,$datarray,$item) {
4229
4230         if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4231                 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4232                         intval(CONTACT_IS_SHARING),
4233                         intval($contact['id'])
4234                 );
4235         }
4236         else {
4237                 contact_remove($contact['id']);
4238         }
4239 }
4240
4241 function lose_sharer($importer,$contact,$datarray,$item) {
4242
4243         if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4244                 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4245                         intval(CONTACT_IS_FOLLOWER),
4246                         intval($contact['id'])
4247                 );
4248         }
4249         else {
4250                 contact_remove($contact['id']);
4251         }
4252 }
4253
4254
4255 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4256
4257         $a = get_app();
4258
4259         if(is_array($importer)) {
4260                 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4261                         intval($importer['uid'])
4262                 );
4263         }
4264
4265         // Diaspora has different message-ids in feeds than they do
4266         // through the direct Diaspora protocol. If we try and use
4267         // the feed, we'll get duplicates. So don't.
4268
4269         if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4270                 return;
4271
4272         $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4273
4274         // Use a single verify token, even if multiple hubs
4275
4276         $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4277
4278         $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4279
4280         logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: '  . $push_url . ' with verifier ' . $verify_token);
4281
4282         if(! strlen($contact['hub-verify'])) {
4283                 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4284                         dbesc($verify_token),
4285                         intval($contact['id'])
4286                 );
4287         }
4288
4289         post_url($url,$params);
4290
4291         logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4292
4293         return;
4294
4295 }
4296
4297
4298 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4299         $o = '';
4300         if(! $tag)
4301                 return $o;
4302         $name = xmlify($name);
4303         $uri = xmlify($uri);
4304         $h = intval($h);
4305         $w = intval($w);
4306         $photo = xmlify($photo);
4307
4308
4309         $o .= "<$tag>\r\n";
4310         $o .= "<name>$name</name>\r\n";
4311         $o .= "<uri>$uri</uri>\r\n";
4312         $o .= '<link rel="photo"  type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4313         $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4314
4315         call_hooks('atom_author', $o);
4316
4317         $o .= "</$tag>\r\n";
4318         return $o;
4319 }
4320
4321 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4322
4323         $a = get_app();
4324
4325         if(! $item['parent'])
4326                 return;
4327
4328         if($item['deleted'])
4329                 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4330
4331
4332         if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4333                 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4334         else
4335                 $body = $item['body'];
4336
4337
4338         $o = "\r\n\r\n<entry>\r\n";
4339
4340         if(is_array($author))
4341                 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4342         else
4343                 $o .= atom_author('author',(($item['author-name']) ? $item['author-name'] : $item['name']),(($item['author-link']) ? $item['author-link'] : $item['url']),80,80,(($item['author-avatar']) ? $item['author-avatar'] : $item['thumb']));
4344         if(strlen($item['owner-name']))
4345                 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4346
4347         if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4348                 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4349                 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' .  xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
4350         }
4351
4352         $htmlbody = $body;
4353
4354         if ($item['title'] != "")
4355                 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4356
4357         $htmlbody = bbcode(bb_remove_share_information($htmlbody), false, false, 7);
4358
4359         $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4360         $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4361         $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4362         $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4363         $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4364         $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4365         $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
4366
4367
4368         if($comment)
4369                 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4370
4371         if($item['location']) {
4372                 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4373                 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4374         }
4375
4376         if($item['coord'])
4377                 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4378
4379         if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4380                 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4381
4382         if($item['extid'])
4383                 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4384         if($item['bookmark'])
4385                 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4386
4387         if($item['app'])
4388                 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4389
4390         if($item['guid'])
4391                 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4392
4393         if($item['signed_text']) {
4394                 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4395                 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4396         }
4397
4398         $verb = construct_verb($item);
4399         $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4400         $actobj = construct_activity_object($item);
4401         if(strlen($actobj))
4402                 $o .= $actobj;
4403         $actarg = construct_activity_target($item);
4404         if(strlen($actarg))
4405                 $o .= $actarg;
4406
4407         $tags = item_getfeedtags($item);
4408         if(count($tags)) {
4409                 foreach($tags as $t) {
4410                         $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4411                 }
4412         }
4413
4414         $o .= item_getfeedattach($item);
4415
4416         $mentioned = get_mentions($item);
4417         if($mentioned)
4418                 $o .= $mentioned;
4419
4420         call_hooks('atom_entry', $o);
4421
4422         $o .= '</entry>' . "\r\n";
4423
4424         return $o;
4425 }
4426
4427 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4428
4429         if(get_config('system','disable_embedded'))
4430                 return $s;
4431
4432         $a = get_app();
4433
4434         logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4435         $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4436
4437         $orig_body = $s;
4438         $new_body = '';
4439
4440         $img_start = strpos($orig_body, '[img');
4441         $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4442         $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4443         while( ($img_st_close !== false) && ($img_len !== false) ) {
4444
4445                 $img_st_close++; // make it point to AFTER the closing bracket
4446                 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4447
4448                 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4449
4450
4451                 if(stristr($image , $site . '/photo/')) {
4452                         // Only embed locally hosted photos
4453                         $replace = false;
4454                         $i = basename($image);
4455                         $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4456                         $x = strpos($i,'-');
4457
4458                         if($x) {
4459                                 $res = substr($i,$x+1);
4460                                 $i = substr($i,0,$x);
4461                                 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4462                                         dbesc($i),
4463                                         intval($res),
4464                                         intval($uid)
4465                                 );
4466                                 if($r) {
4467
4468                                         // Check to see if we should replace this photo link with an embedded image
4469                                         // 1. No need to do so if the photo is public
4470                                         // 2. If there's a contact-id provided, see if they're in the access list
4471                                         //    for the photo. If so, embed it.
4472                                         // 3. Otherwise, if we have an item, see if the item permissions match the photo
4473                                         //    permissions, regardless of order but first check to see if they're an exact
4474                                         //    match to save some processing overhead.
4475
4476                                         if(has_permissions($r[0])) {
4477                                                 if($cid) {
4478                                                         $recips = enumerate_permissions($r[0]);
4479                                                         if(in_array($cid, $recips)) {
4480                                                                 $replace = true;
4481                                                         }
4482                                                 }
4483                                                 elseif($item) {
4484                                                         if(compare_permissions($item,$r[0]))
4485                                                                 $replace = true;
4486                                                 }
4487                                         }
4488                                         if($replace) {
4489                                                 $data = $r[0]['data'];
4490                                                 $type = $r[0]['type'];
4491
4492                                                 // If a custom width and height were specified, apply before embedding
4493                                                 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4494                                                         logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4495
4496                                                         $width = intval($match[1]);
4497                                                         $height = intval($match[2]);
4498
4499                                                         $ph = new Photo($data, $type);
4500                                                         if($ph->is_valid()) {
4501                                                                 $ph->scaleImage(max($width, $height));
4502                                                                 $data = $ph->imageString();
4503                                                                 $type = $ph->getType();
4504                                                         }
4505                                                 }
4506
4507                                                 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4508                                                 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4509                                                 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4510                                         }
4511                                 }
4512                         }
4513                 }
4514
4515                 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4516                 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4517                 if($orig_body === false)
4518                         $orig_body = '';
4519
4520                 $img_start = strpos($orig_body, '[img');
4521                 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4522                 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4523         }
4524
4525         $new_body = $new_body . $orig_body;
4526
4527         return($new_body);
4528 }
4529
4530
4531 function has_permissions($obj) {
4532         if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4533                 return true;
4534         return false;
4535 }
4536
4537 function compare_permissions($obj1,$obj2) {
4538         // first part is easy. Check that these are exactly the same.
4539         if(($obj1['allow_cid'] == $obj2['allow_cid'])
4540                 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4541                 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4542                 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4543                 return true;
4544
4545         // This is harder. Parse all the permissions and compare the resulting set.
4546
4547         $recipients1 = enumerate_permissions($obj1);
4548         $recipients2 = enumerate_permissions($obj2);
4549         sort($recipients1);
4550         sort($recipients2);
4551         if($recipients1 == $recipients2)
4552                 return true;
4553         return false;
4554 }
4555
4556 // returns an array of contact-ids that are allowed to see this object
4557
4558 function enumerate_permissions($obj) {
4559         require_once('include/group.php');
4560         $allow_people = expand_acl($obj['allow_cid']);
4561         $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4562         $deny_people  = expand_acl($obj['deny_cid']);
4563         $deny_groups  = expand_groups(expand_acl($obj['deny_gid']));
4564         $recipients   = array_unique(array_merge($allow_people,$allow_groups));
4565         $deny         = array_unique(array_merge($deny_people,$deny_groups));
4566         $recipients   = array_diff($recipients,$deny);
4567         return $recipients;
4568 }
4569
4570 function item_getfeedtags($item) {
4571         $ret = array();
4572         $matches = false;
4573         $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4574         if($cnt) {
4575                 for($x = 0; $x < $cnt; $x ++) {
4576                         if($matches[1][$x])
4577                                 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4578                 }
4579         }
4580         $matches = false;
4581         $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4582         if($cnt) {
4583                 for($x = 0; $x < $cnt; $x ++) {
4584                         if($matches[1][$x])
4585                                 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4586                 }
4587         }
4588         return $ret;
4589 }
4590
4591 function item_getfeedattach($item) {
4592         $ret = '';
4593         $arr = explode('[/attach],',$item['attach']);
4594         if(count($arr)) {
4595                 foreach($arr as $r) {
4596                         $matches = false;
4597                         $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4598                         if($cnt) {
4599                                 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4600                                 if(intval($matches[2]))
4601                                         $ret .= 'length="' . intval($matches[2]) . '" ';
4602                                 if($matches[4] !== ' ')
4603                                         $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4604                                 $ret .= ' />' . "\r\n";
4605                         }
4606                 }
4607         }
4608         return $ret;
4609 }
4610
4611
4612
4613 function item_expire($uid, $days, $network = "", $force = false) {
4614
4615         if((! $uid) || ($days < 1))
4616                 return;
4617
4618         // $expire_network_only = save your own wall posts
4619         // and just expire conversations started by others
4620
4621         $expire_network_only = get_pconfig($uid,'expire','network_only');
4622         $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4623
4624         if ($network != "") {
4625                 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4626                 // There is an index "uid_network_received" but not "uid_network_created"
4627                 // This avoids the creation of another index just for one purpose.
4628                 // And it doesn't really matter wether to look at "received" or "created"
4629                 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4630         } else
4631                 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4632
4633         $r = q("SELECT * FROM `item`
4634                 WHERE `uid` = %d $range
4635                 AND `id` = `parent`
4636                 $sql_extra
4637                 AND `deleted` = 0",
4638                 intval($uid),
4639                 intval($days)
4640         );
4641
4642         if(! count($r))
4643                 return;
4644
4645         $expire_items = get_pconfig($uid, 'expire','items');
4646         $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4647
4648         // Forcing expiring of items - but not notes and marked items
4649         if ($force)
4650                 $expire_items = true;
4651
4652         $expire_notes = get_pconfig($uid, 'expire','notes');
4653         $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4654
4655         $expire_starred = get_pconfig($uid, 'expire','starred');
4656         $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4657
4658         $expire_photos = get_pconfig($uid, 'expire','photos');
4659         $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4660
4661         logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4662
4663         foreach($r as $item) {
4664
4665                 // don't expire filed items
4666
4667                 if(strpos($item['file'],'[') !== false)
4668                         continue;
4669
4670                 // Only expire posts, not photos and photo comments
4671
4672                 if($expire_photos==0 && strlen($item['resource-id']))
4673                         continue;
4674                 if($expire_starred==0 && intval($item['starred']))
4675                         continue;
4676                 if($expire_notes==0 && $item['type']=='note')
4677                         continue;
4678                 if($expire_items==0 && $item['type']!='note')
4679                         continue;
4680
4681                 drop_item($item['id'],false);
4682         }
4683
4684         proc_run('php',"include/notifier.php","expire","$uid");
4685
4686 }
4687
4688
4689 function drop_items($items) {
4690         $uid = 0;
4691
4692         if(! local_user() && ! remote_user())
4693                 return;
4694
4695         if(count($items)) {
4696                 foreach($items as $item) {
4697                         $owner = drop_item($item,false);
4698                         if($owner && ! $uid)
4699                                 $uid = $owner;
4700                 }
4701         }
4702
4703         // multiple threads may have been deleted, send an expire notification
4704
4705         if($uid)
4706                 proc_run('php',"include/notifier.php","expire","$uid");
4707 }
4708
4709
4710 function drop_item($id,$interactive = true) {
4711
4712         $a = get_app();
4713
4714         // locate item to be deleted
4715
4716         $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4717                 intval($id)
4718         );
4719
4720         if(! count($r)) {
4721                 if(! $interactive)
4722                         return 0;
4723                 notice( t('Item not found.') . EOL);
4724                 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4725         }
4726
4727         $item = $r[0];
4728
4729         $owner = $item['uid'];
4730
4731         $cid = 0;
4732
4733         // check if logged in user is either the author or owner of this item
4734
4735         if(is_array($_SESSION['remote'])) {
4736                 foreach($_SESSION['remote'] as $visitor) {
4737                         if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4738                                 $cid = $visitor['cid'];
4739                                 break;
4740                         }
4741                 }
4742         }
4743
4744
4745         if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4746
4747                 // Check if we should do HTML-based delete confirmation
4748                 if($_REQUEST['confirm']) {
4749                         // <form> can't take arguments in its "action" parameter
4750                         // so add any arguments as hidden inputs
4751                         $query = explode_querystring($a->query_string);
4752                         $inputs = array();
4753                         foreach($query['args'] as $arg) {
4754                                 if(strpos($arg, 'confirm=') === false) {
4755                                         $arg_parts = explode('=', $arg);
4756                                         $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4757                                 }
4758                         }
4759
4760                         return replace_macros(get_markup_template('confirm.tpl'), array(
4761                                 '$method' => 'get',
4762                                 '$message' => t('Do you really want to delete this item?'),
4763                                 '$extra_inputs' => $inputs,
4764                                 '$confirm' => t('Yes'),
4765                                 '$confirm_url' => $query['base'],
4766                                 '$confirm_name' => 'confirmed',
4767                                 '$cancel' => t('Cancel'),
4768                         ));
4769                 }
4770                 // Now check how the user responded to the confirmation query
4771                 if($_REQUEST['canceled']) {
4772                         goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4773                 }
4774
4775                 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4776                 // delete the item
4777
4778                 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4779                         dbesc(datetime_convert()),
4780                         dbesc(datetime_convert()),
4781                         intval($item['id'])
4782                 );
4783                 create_tags_from_item($item['id']);
4784                 create_files_from_item($item['id']);
4785                 delete_thread($item['id'], $item['parent-uri']);
4786
4787                 // clean up categories and tags so they don't end up as orphans
4788
4789                 $matches = false;
4790                 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4791                 if($cnt) {
4792                         foreach($matches as $mtch) {
4793                                 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4794                         }
4795                 }
4796
4797                 $matches = false;
4798
4799                 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4800                 if($cnt) {
4801                         foreach($matches as $mtch) {
4802                                 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4803                         }
4804                 }
4805
4806                 // If item is a link to a photo resource, nuke all the associated photos
4807                 // (visitors will not have photo resources)
4808                 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4809                 // generate a resource-id and therefore aren't intimately linked to the item.
4810
4811                 if(strlen($item['resource-id'])) {
4812                         q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4813                                 dbesc($item['resource-id']),
4814                                 intval($item['uid'])
4815                         );
4816                         // ignore the result
4817                 }
4818
4819                 // If item is a link to an event, nuke the event record.
4820
4821                 if(intval($item['event-id'])) {
4822                         q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4823                                 intval($item['event-id']),
4824                                 intval($item['uid'])
4825                         );
4826                         // ignore the result
4827                 }
4828
4829                 // If item has attachments, drop them
4830
4831                 foreach(explode(",",$item['attach']) as $attach){
4832                         preg_match("|attach/(\d+)|", $attach, $matches);
4833                         q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4834                                 intval($matches[1]),
4835                                 local_user()
4836                         );
4837                         // ignore the result
4838                 }
4839
4840
4841                 // clean up item_id and sign meta-data tables
4842
4843                 /*
4844                 // Old code - caused very long queries and warning entries in the mysql logfiles:
4845
4846                 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4847                         intval($item['id']),
4848                         intval($item['uid'])
4849                 );
4850
4851                 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4852                         intval($item['id']),
4853                         intval($item['uid'])
4854                 );
4855                 */
4856
4857                 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4858
4859                 // Creating list of parents
4860                 $r = q("select id from item where parent = %d and uid = %d",
4861                         intval($item['id']),
4862                         intval($item['uid'])
4863                 );
4864
4865                 $parentid = "";
4866
4867                 foreach ($r AS $row) {
4868                         if ($parentid != "")
4869                                 $parentid .= ", ";
4870
4871                         $parentid .= $row["id"];
4872                 }
4873
4874                 // Now delete them
4875                 if ($parentid != "") {
4876                         $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4877
4878                         $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4879                 }
4880
4881                 // If it's the parent of a comment thread, kill all the kids
4882
4883                 if($item['uri'] == $item['parent-uri']) {
4884                         $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4885                                 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4886                                 dbesc(datetime_convert()),
4887                                 dbesc(datetime_convert()),
4888                                 dbesc($item['parent-uri']),
4889                                 intval($item['uid'])
4890                         );
4891                         create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4892                         create_files_from_itemuri($item['parent-uri'], $item['uid']);
4893                         delete_thread_uri($item['parent-uri'], $item['uid']);
4894                         // ignore the result
4895                 }
4896                 else {
4897                         // ensure that last-child is set in case the comment that had it just got wiped.
4898                         q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4899                                 dbesc(datetime_convert()),
4900                                 dbesc($item['parent-uri']),
4901                                 intval($item['uid'])
4902                         );
4903                         // who is the last child now?
4904                         $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d ORDER BY `edited` DESC LIMIT 1",
4905                                 dbesc($item['parent-uri']),
4906                                 intval($item['uid'])
4907                         );
4908                         if(count($r)) {
4909                                 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4910                                         intval($r[0]['id'])
4911                                 );
4912                         }
4913
4914                         // Add a relayable_retraction signature for Diaspora.
4915                         store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4916                 }
4917
4918                 $drop_id = intval($item['id']);
4919
4920                 // send the notification upstream/downstream as the case may be
4921
4922                 proc_run('php',"include/notifier.php","drop","$drop_id");
4923
4924                 if(! $interactive)
4925                         return $owner;
4926                 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4927                 //NOTREACHED
4928         }
4929         else {
4930                 if(! $interactive)
4931                         return 0;
4932                 notice( t('Permission denied.') . EOL);
4933                 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4934                 //NOTREACHED
4935         }
4936
4937 }
4938
4939
4940 function first_post_date($uid,$wall = false) {
4941         $r = q("select id, created from item
4942                 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4943                 and id = parent
4944                 order by created asc limit 1",
4945                 intval($uid),
4946                 intval($wall ? 1 : 0)
4947         );
4948         if(count($r)) {
4949 //              logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4950                 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4951         }
4952         return false;
4953 }
4954
4955 /* modified posted_dates() {below} to arrange the list in years */
4956 function list_post_dates($uid, $wall) {
4957         $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4958         
4959         $dthen = first_post_date($uid, $wall);        
4960         if(! $dthen)
4961                 return array();
4962         
4963         // Set the start and end date to the beginning of the month
4964         $dnow = substr($dnow,0,8).'01';
4965         $dthen = substr($dthen,0,8).'01';
4966         
4967         $ret = array();
4968         
4969         // Starting with the current month, get the first and last days of every
4970         // month down to and including the month of the first post
4971         while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4972                 $dyear = intval(substr($dnow,0,4));
4973                 $dstart = substr($dnow,0,8) . '01';
4974                 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4975                 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4976                 $end_month = datetime_convert('','',$dend,'Y-m-d');
4977                 $str = day_translate(datetime_convert('','',$dnow,'F'));
4978                 if(! $ret[$dyear])
4979                         $ret[$dyear] = array();
4980                 $ret[$dyear][] = array($str,$end_month,$start_month);
4981                 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4982         }
4983         return $ret;
4984 }
4985
4986 function posted_dates($uid,$wall) {
4987         $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4988
4989         $dthen = first_post_date($uid,$wall);
4990         if(! $dthen)
4991                 return array();
4992
4993         // Set the start and end date to the beginning of the month
4994         $dnow = substr($dnow,0,8).'01';
4995         $dthen = substr($dthen,0,8).'01';
4996
4997         $ret = array();
4998         // Starting with the current month, get the first and last days of every
4999         // month down to and including the month of the first post
5000         while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5001                 $dstart = substr($dnow,0,8) . '01';
5002                 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5003                 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5004                 $end_month = datetime_convert('','',$dend,'Y-m-d');
5005                 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
5006                 $ret[] = array($str,$end_month,$start_month);
5007                 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5008         }
5009         return $ret;
5010 }
5011
5012
5013 function posted_date_widget($url,$uid,$wall) {
5014         $o = '';
5015
5016         if(! feature_enabled($uid,'archives'))
5017                 return $o;
5018
5019         // For former Facebook folks that left because of "timeline"
5020
5021 /*      if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
5022                 return $o;*/
5023         
5024         $visible_years = get_pconfig($uid,'system','archive_visible_years');
5025         if(! $visible_years)
5026                 $visible_years = 5;     
5027         
5028         $ret = list_post_dates($uid,$wall);
5029         
5030         if(! count($ret))
5031                 return $o;
5032
5033         $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
5034         $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
5035         
5036         $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
5037                 '$title' => t('Archives'),
5038                 '$size' => $visible_years,
5039                 '$cutoff_year' => $cutoff_year,
5040                 '$cutoff' => $cutoff,
5041                 '$url' => $url,
5042                 '$dates' => $ret,
5043                 '$showmore' => t('show more')
5044
5045         ));
5046         return $o;
5047 }
5048
5049 function store_diaspora_retract_sig($item, $user, $baseurl) {
5050         // Note that we can't add a target_author_signature
5051         // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
5052         // the comment, that means we're the home of the post, and Diaspora will only
5053         // check the parent_author_signature of retractions that it doesn't have to relay further
5054         //
5055         // I don't think this function gets called for an "unlike," but I'll check anyway
5056
5057         $enabled = intval(get_config('system','diaspora_enabled'));
5058         if(! $enabled) {
5059                 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
5060                 return;
5061         }
5062
5063         logger('drop_item: storing diaspora retraction signature');
5064
5065         $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
5066
5067         if(local_user() == $item['uid']) {
5068
5069                 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
5070                 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
5071         }
5072         else {
5073                 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
5074                         $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
5075                 );
5076                 if(count($r)) {
5077                         // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
5078                         // only handles DFRN deletes
5079                         $handle_baseurl_start = strpos($r['url'],'://') + 3;
5080                         $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
5081                         $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
5082                         $authorsig = '';
5083                 }
5084         }
5085
5086         if(isset($handle))
5087                 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
5088                         intval($item['id']),
5089                         dbesc($signed_text),
5090                         dbesc($authorsig),
5091                         dbesc($handle)
5092                 );
5093
5094         return;
5095 }