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