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