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