]> git.mxchange.org Git - friendica.git/blob - include/items.php
Merge pull request #1503 from fraengii/20150408-H-de-fraengii
[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                         ));
1572                         logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1573                 }
1574         } else {
1575                 update_thread($parent_id);
1576                 add_shadow_entry($arr);
1577         }
1578
1579         if ($notify)
1580                 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1581
1582         return $current_post;
1583 }
1584
1585 function item_body_set_hashtags(&$item) {
1586
1587         $tags = get_tags($item["body"]);
1588
1589         // No hashtags?
1590         if(!count($tags))
1591                 return(false);
1592
1593         // This sorting is important when there are hashtags that are part of other hashtags
1594         // Otherwise there could be problems with hashtags like #test and #test2
1595         rsort($tags);
1596
1597         $a = get_app();
1598
1599         $URLSearchString = "^\[\]";
1600
1601         // All hashtags should point to the home server
1602         //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1603         //              "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1604
1605         //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1606         //              "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1607
1608         // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1609         $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1610                 function ($match){
1611                         return("[url=".$match[1]."]".str_replace("#", "&num;", $match[2])."[/url]");
1612                 },$item["body"]);
1613
1614         $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1615                 function ($match){
1616                         return("[bookmark=".$match[1]."]".str_replace("#", "&num;", $match[2])."[/bookmark]");
1617                 },$item["body"]);
1618
1619         $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1620                 function ($match){
1621                         return("[attachment ".str_replace("#", "&num;", $match[1])."]".$match[2]."[/attachment]");
1622                 },$item["body"]);
1623
1624         // Repair recursive urls
1625         $item["body"] = preg_replace("/&num;\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1626                         "&num;$2", $item["body"]);
1627
1628         foreach($tags as $tag) {
1629                 if(strpos($tag,'#') !== 0)
1630                         continue;
1631
1632                 if(strpos($tag,'[url='))
1633                         continue;
1634
1635                 $basetag = str_replace('_',' ',substr($tag,1));
1636
1637                 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1638
1639                 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1640
1641                 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1642                         if(strlen($item["tag"]))
1643                                 $item["tag"] = ','.$item["tag"];
1644                         $item["tag"] = $newtag.$item["tag"];
1645                 }
1646         }
1647
1648         // Convert back the masked hashtags
1649         $item["body"] = str_replace("&num;", "#", $item["body"]);
1650 }
1651
1652 function get_item_guid($id) {
1653         $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1654         if (count($r))
1655                 return($r[0]["guid"]);
1656         else
1657                 return("");
1658 }
1659
1660 function get_item_id($guid, $uid = 0) {
1661
1662         $nick = "";
1663         $id = 0;
1664
1665         if ($uid == 0)
1666                 $uid == local_user();
1667
1668         // Does the given user have this item?
1669         if ($uid) {
1670                 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1671                         WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1672                                 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1673                 if (count($r)) {
1674                         $id = $r[0]["id"];
1675                         $nick = $r[0]["nickname"];
1676                 }
1677         }
1678
1679         // Or is it anywhere on the server?
1680         if ($nick == "") {
1681                 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1682                         WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1683                                 AND `item`.`allow_cid` = ''  AND `item`.`allow_gid` = ''
1684                                 AND `item`.`deny_cid`  = '' AND `item`.`deny_gid`  = ''
1685                                 AND `item`.`private` = 0 AND `item`.`wall` = 1
1686                                 AND `item`.`guid` = '%s'", dbesc($guid));
1687                 if (count($r)) {
1688                         $id = $r[0]["id"];
1689                         $nick = $r[0]["nickname"];
1690                 }
1691         }
1692         return(array("nick" => $nick, "id" => $id));
1693 }
1694
1695 // return - test
1696 function get_item_contact($item,$contacts) {
1697         if(! count($contacts) || (! is_array($item)))
1698                 return false;
1699         foreach($contacts as $contact) {
1700                 if($contact['id'] == $item['contact-id']) {
1701                         return $contact;
1702                         break; // NOTREACHED
1703                 }
1704         }
1705         return false;
1706 }
1707
1708 /**
1709  * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1710  * @param int $uid
1711  * @param int $item_id
1712  * @return bool true if item was deleted, else false
1713  */
1714 function tag_deliver($uid,$item_id) {
1715
1716         //
1717
1718         $a = get_app();
1719
1720         $mention = false;
1721
1722         $u = q("select * from user where uid = %d limit 1",
1723                 intval($uid)
1724         );
1725         if(! count($u))
1726                 return;
1727
1728         $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1729         $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1730
1731
1732         $i = q("select * from item where id = %d and uid = %d limit 1",
1733                 intval($item_id),
1734                 intval($uid)
1735         );
1736         if(! count($i))
1737                 return;
1738
1739         $item = $i[0];
1740
1741         $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1742
1743         // Diaspora uses their own hardwired link URL in @-tags
1744         // instead of the one we supply with webfinger
1745
1746         $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1747
1748         $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1749         if($cnt) {
1750                 foreach($matches as $mtch) {
1751                         if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1752                                 $mention = true;
1753                                 logger('tag_deliver: mention found: ' . $mtch[2]);
1754                         }
1755                 }
1756         }
1757
1758         if(! $mention){
1759                 if ( ($community_page || $prvgroup) &&
1760                           (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1761                         // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1762                         // delete it!
1763                         logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1764                         q("DELETE FROM item WHERE id = %d and uid = %d",
1765                                 intval($item_id),
1766                                 intval($uid)
1767                         );
1768                         return true;
1769                 }
1770                 return;
1771         }
1772
1773
1774         // send a notification
1775
1776         // use a local photo if we have one
1777
1778         $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1779                 intval($u[0]['uid']),
1780                 dbesc(normalise_link($item['author-link']))
1781         );
1782         $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1783
1784
1785         require_once('include/enotify.php');
1786         notification(array(
1787                 'type'         => NOTIFY_TAGSELF,
1788                 'notify_flags' => $u[0]['notify-flags'],
1789                 'language'     => $u[0]['language'],
1790                 'to_name'      => $u[0]['username'],
1791                 'to_email'     => $u[0]['email'],
1792                 'uid'          => $u[0]['uid'],
1793                 'item'         => $item,
1794                 'link'         => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1795                 'source_name'  => $item['author-name'],
1796                 'source_link'  => $item['author-link'],
1797                 'source_photo' => $photo,
1798                 'verb'         => ACTIVITY_TAG,
1799                 'otype'        => 'item',
1800                 'parent'       => $item['parent']
1801         ));
1802
1803
1804         $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1805
1806         call_hooks('tagged', $arr);
1807
1808         if((! $community_page) && (! $prvgroup))
1809                 return;
1810
1811
1812         // tgroup delivery - setup a second delivery chain
1813         // prevent delivery looping - only proceed
1814         // if the message originated elsewhere and is a top-level post
1815
1816         if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1817                 return;
1818
1819         // now change this copy of the post to a forum head message and deliver to all the tgroup members
1820
1821
1822         $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1823                 intval($u[0]['uid'])
1824         );
1825         if(! count($c))
1826                 return;
1827
1828         // also reset all the privacy bits to the forum default permissions
1829
1830         $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1831
1832         $forum_mode = (($prvgroup) ? 2 : 1);
1833
1834         q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1835                 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s'  where id = %d",
1836                 intval($forum_mode),
1837                 dbesc($c[0]['name']),
1838                 dbesc($c[0]['url']),
1839                 dbesc($c[0]['thumb']),
1840                 intval($private),
1841                 dbesc($u[0]['allow_cid']),
1842                 dbesc($u[0]['allow_gid']),
1843                 dbesc($u[0]['deny_cid']),
1844                 dbesc($u[0]['deny_gid']),
1845                 intval($item_id)
1846         );
1847         update_thread($item_id);
1848
1849         proc_run('php','include/notifier.php','tgroup',$item_id);
1850
1851 }
1852
1853
1854
1855 function tgroup_check($uid,$item) {
1856
1857         $a = get_app();
1858
1859         $mention = false;
1860
1861         // check that the message originated elsewhere and is a top-level post
1862
1863         if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1864                 return false;
1865
1866
1867         $u = q("select * from user where uid = %d limit 1",
1868                 intval($uid)
1869         );
1870         if(! count($u))
1871                 return false;
1872
1873         $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1874         $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1875
1876
1877         $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1878
1879         // Diaspora uses their own hardwired link URL in @-tags
1880         // instead of the one we supply with webfinger
1881
1882         $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1883
1884         $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1885         if($cnt) {
1886                 foreach($matches as $mtch) {
1887                         if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1888                                 $mention = true;
1889                                 logger('tgroup_check: mention found: ' . $mtch[2]);
1890                         }
1891                 }
1892         }
1893
1894         if(! $mention)
1895                 return false;
1896
1897         if((! $community_page) && (! $prvgroup))
1898                 return false;
1899
1900
1901
1902         return true;
1903
1904 }
1905
1906
1907
1908
1909
1910
1911 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1912
1913         $a = get_app();
1914
1915         $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1916
1917         if($contact['duplex'] && $contact['dfrn-id'])
1918                 $idtosend = '0:' . $orig_id;
1919         if($contact['duplex'] && $contact['issued-id'])
1920                 $idtosend = '1:' . $orig_id;
1921
1922         $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1923
1924         $rino_enable = get_config('system','rino_encrypt');
1925
1926         if(! $rino_enable)
1927                 $rino = 0;
1928
1929         $ssl_val = intval(get_config('system','ssl_policy'));
1930         $ssl_policy = '';
1931
1932         switch($ssl_val){
1933                 case SSL_POLICY_FULL:
1934                         $ssl_policy = 'full';
1935                         break;
1936                 case SSL_POLICY_SELFSIGN:
1937                         $ssl_policy = 'self';
1938                         break;
1939                 case SSL_POLICY_NONE:
1940                 default:
1941                         $ssl_policy = 'none';
1942                         break;
1943         }
1944
1945         $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1946
1947         logger('dfrn_deliver: ' . $url);
1948
1949         $xml = fetch_url($url);
1950
1951         $curl_stat = $a->get_curl_code();
1952         if(! $curl_stat)
1953                 return(-1); // timed out
1954
1955         logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1956
1957         if(! $xml)
1958                 return 3;
1959
1960         if(strpos($xml,'<?xml') === false) {
1961                 logger('dfrn_deliver: no valid XML returned');
1962                 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1963                 return 3;
1964         }
1965
1966         $res = parse_xml_string($xml);
1967
1968         if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1969                 return (($res->status) ? $res->status : 3);
1970
1971         $postvars     = array();
1972         $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1973         $challenge    = hex2bin((string) $res->challenge);
1974         $perm         = (($res->perm) ? $res->perm : null);
1975         $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1976         $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1977         $page         = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1978
1979         if($owner['page-flags'] == PAGE_PRVGROUP)
1980                 $page = 2;
1981
1982         $final_dfrn_id = '';
1983
1984         if($perm) {
1985                 if((($perm == 'rw') && (! intval($contact['writable'])))
1986                 || (($perm == 'r') && (intval($contact['writable'])))) {
1987                         q("update contact set writable = %d where id = %d",
1988                                 intval(($perm == 'rw') ? 1 : 0),
1989                                 intval($contact['id'])
1990                         );
1991                         $contact['writable'] = (string) 1 - intval($contact['writable']);
1992                 }
1993         }
1994
1995         if(($contact['duplex'] && strlen($contact['pubkey']))
1996                 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1997                 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1998                 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1999                 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
2000         }
2001         else {
2002                 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
2003                 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
2004         }
2005
2006         $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
2007
2008         if(strpos($final_dfrn_id,':') == 1)
2009                 $final_dfrn_id = substr($final_dfrn_id,2);
2010
2011         if($final_dfrn_id != $orig_id) {
2012                 logger('dfrn_deliver: wrong dfrn_id.');
2013                 // did not decode properly - cannot trust this site
2014                 return 3;
2015         }
2016
2017         $postvars['dfrn_id']      = $idtosend;
2018         $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
2019         if($dissolve)
2020                 $postvars['dissolve'] = '1';
2021
2022
2023         if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2024                 $postvars['data'] = $atom;
2025                 $postvars['perm'] = 'rw';
2026         }
2027         else {
2028                 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
2029                 $postvars['perm'] = 'r';
2030         }
2031
2032         $postvars['ssl_policy'] = $ssl_policy;
2033
2034         if($page)
2035                 $postvars['page'] = $page;
2036
2037         if($rino && $rino_allowed && (! $dissolve)) {
2038                 $key = substr(random_string(),0,16);
2039                 $data = bin2hex(aes_encrypt($postvars['data'],$key));
2040                 $postvars['data'] = $data;
2041                 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2042
2043
2044                 if($dfrn_version >= 2.1) {
2045                         if(($contact['duplex'] && strlen($contact['pubkey']))
2046                                 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2047                                 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2048
2049                                 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2050                         }
2051                         else {
2052                                 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2053                         }
2054                 }
2055                 else {
2056                         if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2057                                 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2058                         }
2059                         else {
2060                                 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2061                         }
2062                 }
2063
2064                 logger('md5 rawkey ' . md5($postvars['key']));
2065
2066                 $postvars['key'] = bin2hex($postvars['key']);
2067         }
2068
2069         logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2070
2071         $xml = post_url($contact['notify'],$postvars);
2072
2073         logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2074
2075         $curl_stat = $a->get_curl_code();
2076         if((! $curl_stat) || (! strlen($xml)))
2077                 return(-1); // timed out
2078
2079         if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2080                 return(-1);
2081
2082         if(strpos($xml,'<?xml') === false) {
2083                 logger('dfrn_deliver: phase 2: no valid XML returned');
2084                 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2085                 return 3;
2086         }
2087
2088         if($contact['term-date'] != '0000-00-00 00:00:00') {
2089                 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2090                 require_once('include/Contact.php');
2091                 unmark_for_death($contact);
2092         }
2093
2094         $res = parse_xml_string($xml);
2095
2096         return $res->status;
2097 }
2098
2099
2100 /*
2101   This function returns true if $update has an edited timestamp newer
2102   than $existing, i.e. $update contains new data which should override
2103   what's already there.  If there is no timestamp yet, the update is
2104   assumed to be newer.  If the update has no timestamp, the existing
2105   item is assumed to be up-to-date.  If the timestamps are equal it
2106   assumes the update has been seen before and should be ignored.
2107   */
2108 function edited_timestamp_is_newer($existing, $update) {
2109     if (!x($existing,'edited') || !$existing['edited']) {
2110         return true;
2111     }
2112     if (!x($update,'edited') || !$update['edited']) {
2113         return false;
2114     }
2115     $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2116     $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2117     return (strcmp($existing_edited, $update_edited) < 0);
2118 }
2119
2120 /**
2121  *
2122  * consume_feed - process atom feed and update anything/everything we might need to update
2123  *
2124  * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2125  *
2126  * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2127  *             It is this person's stuff that is going to be updated.
2128  * $contact =  the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2129  *             from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2130  *             have a contact record.
2131  * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2132  *        might not) try and subscribe to it.
2133  * $datedir sorts in reverse order
2134  * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2135  *      imported prior to its children being seen in the stream unless we are certain
2136  *      of how the feed is arranged/ordered.
2137  * With $pass = 1, we only pull parent items out of the stream.
2138  * With $pass = 2, we only pull children (comments/likes).
2139  *
2140  * So running this twice, first with pass 1 and then with pass 2 will do the right
2141  * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2142  * model where comments can have sub-threads. That would require some massive sorting
2143  * to get all the feed items into a mostly linear ordering, and might still require
2144  * recursion.
2145  */
2146
2147 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2148
2149         require_once('library/simplepie/simplepie.inc');
2150         require_once('include/contact_selectors.php');
2151
2152         if(! strlen($xml)) {
2153                 logger('consume_feed: empty input');
2154                 return;
2155         }
2156
2157         $feed = new SimplePie();
2158         $feed->set_raw_data($xml);
2159         if($datedir)
2160                 $feed->enable_order_by_date(true);
2161         else
2162                 $feed->enable_order_by_date(false);
2163         $feed->init();
2164
2165         if($feed->error())
2166                 logger('consume_feed: Error parsing XML: ' . $feed->error());
2167
2168         $permalink = $feed->get_permalink();
2169
2170         // Check at the feed level for updated contact name and/or photo
2171
2172         $name_updated  = '';
2173         $new_name = '';
2174         $photo_timestamp = '';
2175         $photo_url = '';
2176         $birthday = '';
2177         $contact_updated = '';
2178
2179         $hubs = $feed->get_links('hub');
2180         logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2181
2182         if(count($hubs))
2183                 $hub = implode(',', $hubs);
2184
2185         $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2186         if(! $rawtags)
2187                 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2188         if($rawtags) {
2189                 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2190                 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2191                         $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2192                         $new_name = $elems['name'][0]['data'];
2193
2194                         // Manually checking for changed contact names
2195                         if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2196                                 $name_updated = date("c");
2197                                 $photo_timestamp = date("c");
2198                         }
2199                 }
2200                 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2201                         if ($photo_timestamp == "")
2202                                 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2203                         $photo_url = $elems['link'][0]['attribs']['']['href'];
2204                 }
2205
2206                 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2207                         $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2208                 }
2209         }
2210
2211         if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2212                 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2213
2214                 $contact_updated = $photo_timestamp;
2215
2216                 require_once("include/Photo.php");
2217                 $photo_failure = false;
2218                 $have_photo = false;
2219
2220                 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2221                         intval($contact['id']),
2222                         intval($contact['uid'])
2223                 );
2224                 if(count($r)) {
2225                         $resource_id = $r[0]['resource-id'];
2226                         $have_photo = true;
2227                 }
2228                 else {
2229                         $resource_id = photo_new_resource();
2230                 }
2231
2232                 $img_str = fetch_url($photo_url,true);
2233                 // guess mimetype from headers or filename
2234                 $type = guess_image_type($photo_url,true);
2235
2236
2237                 $img = new Photo($img_str, $type);
2238                 if($img->is_valid()) {
2239                         if($have_photo) {
2240                                 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2241                                         dbesc($resource_id),
2242                                         intval($contact['id']),
2243                                         intval($contact['uid'])
2244                                 );
2245                         }
2246
2247                         $img->scaleImageSquare(175);
2248
2249                         $hash = $resource_id;
2250                         $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2251
2252                         $img->scaleImage(80);
2253                         $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2254
2255                         $img->scaleImage(48);
2256                         $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2257
2258                         $a = get_app();
2259
2260                         q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2261                                 WHERE `uid` = %d AND `id` = %d",
2262                                 dbesc(datetime_convert()),
2263                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2264                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2265                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2266                                 intval($contact['uid']),
2267                                 intval($contact['id'])
2268                         );
2269                 }
2270         }
2271
2272         if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2273                 if ($name_updated > $contact_updated)
2274                         $contact_updated = $name_updated;
2275
2276                 $r = q("select * from contact where uid = %d and id = %d limit 1",
2277                         intval($contact['uid']),
2278                         intval($contact['id'])
2279                 );
2280
2281                 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2282                         dbesc(notags(trim($new_name))),
2283                         dbesc(datetime_convert()),
2284                         intval($contact['uid']),
2285                         intval($contact['id'])
2286                 );
2287
2288                 // do our best to update the name on content items
2289
2290                 if(count($r)) {
2291                         q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2292                                 dbesc(notags(trim($new_name))),
2293                                 dbesc($r[0]['name']),
2294                                 dbesc($r[0]['url']),
2295                                 intval($contact['uid'])
2296                         );
2297                 }
2298         }
2299
2300         if ($contact_updated AND $new_name AND $photo_url)
2301                 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2302
2303         if(strlen($birthday)) {
2304                 if(substr($birthday,0,4) != $contact['bdyear']) {
2305                         logger('consume_feed: updating birthday: ' . $birthday);
2306
2307                         /**
2308                          *
2309                          * Add new birthday event for this person
2310                          *
2311                          * $bdtext is just a readable placeholder in case the event is shared
2312                          * with others. We will replace it during presentation to our $importer
2313                          * to contain a sparkle link and perhaps a photo.
2314                          *
2315                          */
2316
2317                         $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2318                         $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2319
2320
2321                         $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2322                                 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2323                                 intval($contact['uid']),
2324                                 intval($contact['id']),
2325                                 dbesc(datetime_convert()),
2326                                 dbesc(datetime_convert()),
2327                                 dbesc(datetime_convert('UTC','UTC', $birthday)),
2328                                 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2329                                 dbesc($bdtext),
2330                                 dbesc($bdtext2),
2331                                 dbesc('birthday')
2332                         );
2333
2334
2335                         // update bdyear
2336
2337                         q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2338                                 dbesc(substr($birthday,0,4)),
2339                                 intval($contact['uid']),
2340                                 intval($contact['id'])
2341                         );
2342
2343                         // This function is called twice without reloading the contact
2344                         // Make sure we only create one event. This is why &$contact
2345                         // is a reference var in this function
2346
2347                         $contact['bdyear'] = substr($birthday,0,4);
2348                 }
2349         }
2350
2351         $community_page = 0;
2352         $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2353         if($rawtags) {
2354                 $community_page = intval($rawtags[0]['data']);
2355         }
2356         if(is_array($contact) && intval($contact['forum']) != $community_page) {
2357                 q("update contact set forum = %d where id = %d",
2358                         intval($community_page),
2359                         intval($contact['id'])
2360                 );
2361                 $contact['forum'] = (string) $community_page;
2362         }
2363
2364
2365         // process any deleted entries
2366
2367         $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2368         if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2369                 foreach($del_entries as $dentry) {
2370                         $deleted = false;
2371                         if(isset($dentry['attribs']['']['ref'])) {
2372                                 $uri = $dentry['attribs']['']['ref'];
2373                                 $deleted = true;
2374                                 if(isset($dentry['attribs']['']['when'])) {
2375                                         $when = $dentry['attribs']['']['when'];
2376                                         $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2377                                 }
2378                                 else
2379                                         $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2380                         }
2381                         if($deleted && is_array($contact)) {
2382                                 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2383                                         WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2384                                         dbesc($uri),
2385                                         intval($importer['uid']),
2386                                         intval($contact['id'])
2387                                 );
2388                                 if(count($r)) {
2389                                         $item = $r[0];
2390
2391                                         if(! $item['deleted'])
2392                                                 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2393
2394                                         if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2395                                                 $xo = parse_xml_string($item['object'],false);
2396                                                 $xt = parse_xml_string($item['target'],false);
2397                                                 if($xt->type === ACTIVITY_OBJ_NOTE) {
2398                                                         $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2399                                                                 dbesc($xt->id),
2400                                                                 intval($importer['importer_uid'])
2401                                                         );
2402                                                         if(count($i)) {
2403
2404                                                                 // For tags, the owner cannot remove the tag on the author's copy of the post.
2405
2406                                                                 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2407                                                                 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2408                                                                 $author_copy = (($item['origin']) ? true : false);
2409
2410                                                                 if($owner_remove && $author_copy)
2411                                                                         continue;
2412                                                                 if($author_remove || $owner_remove) {
2413                                                                         $tags = explode(',',$i[0]['tag']);
2414                                                                         $newtags = array();
2415                                                                         if(count($tags)) {
2416                                                                                 foreach($tags as $tag)
2417                                                                                         if(trim($tag) !== trim($xo->body))
2418                                                                                                 $newtags[] = trim($tag);
2419                                                                         }
2420                                                                         q("update item set tag = '%s' where id = %d",
2421                                                                                 dbesc(implode(',',$newtags)),
2422                                                                                 intval($i[0]['id'])
2423                                                                         );
2424                                                                         create_tags_from_item($i[0]['id']);
2425                                                                 }
2426                                                         }
2427                                                 }
2428                                         }
2429
2430                                         if($item['uri'] == $item['parent-uri']) {
2431                                                 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2432                                                         `body` = '', `title` = ''
2433                                                         WHERE `parent-uri` = '%s' AND `uid` = %d",
2434                                                         dbesc($when),
2435                                                         dbesc(datetime_convert()),
2436                                                         dbesc($item['uri']),
2437                                                         intval($importer['uid'])
2438                                                 );
2439                                                 create_tags_from_itemuri($item['uri'], $importer['uid']);
2440                                                 create_files_from_itemuri($item['uri'], $importer['uid']);
2441                                                 update_thread_uri($item['uri'], $importer['uid']);
2442                                         }
2443                                         else {
2444                                                 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2445                                                         `body` = '', `title` = ''
2446                                                         WHERE `uri` = '%s' AND `uid` = %d",
2447                                                         dbesc($when),
2448                                                         dbesc(datetime_convert()),
2449                                                         dbesc($uri),
2450                                                         intval($importer['uid'])
2451                                                 );
2452                                                 create_tags_from_itemuri($uri, $importer['uid']);
2453                                                 create_files_from_itemuri($uri, $importer['uid']);
2454                                                 if($item['last-child']) {
2455                                                         // ensure that last-child is set in case the comment that had it just got wiped.
2456                                                         q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2457                                                                 dbesc(datetime_convert()),
2458                                                                 dbesc($item['parent-uri']),
2459                                                                 intval($item['uid'])
2460                                                         );
2461                                                         // who is the last child now?
2462                                                         $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2463                                                                 ORDER BY `created` DESC LIMIT 1",
2464                                                                         dbesc($item['parent-uri']),
2465                                                                         intval($importer['uid'])
2466                                                         );
2467                                                         if(count($r)) {
2468                                                                 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2469                                                                         intval($r[0]['id'])
2470                                                                 );
2471                                                         }
2472                                                 }
2473                                         }
2474                                 }
2475                         }
2476                 }
2477         }
2478
2479         // Now process the feed
2480
2481         if($feed->get_item_quantity()) {
2482
2483                 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2484
2485         // in inverse date order
2486                 if ($datedir)
2487                         $items = array_reverse($feed->get_items());
2488                 else
2489                         $items = $feed->get_items();
2490
2491
2492                 foreach($items as $item) {
2493
2494                         $is_reply = false;
2495                         $item_id = $item->get_id();
2496                         $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2497                         if(isset($rawthread[0]['attribs']['']['ref'])) {
2498                                 $is_reply = true;
2499                                 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2500                         }
2501
2502                         if(($is_reply) && is_array($contact)) {
2503
2504                                 if($pass == 1)
2505                                         continue;
2506
2507                                 // not allowed to post
2508
2509                                 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2510                                         continue;
2511
2512
2513                                 // Have we seen it? If not, import it.
2514
2515                                 $item_id  = $item->get_id();
2516                                 $datarray = get_atom_elements($feed, $item, $contact);
2517
2518                                 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2519                                         $datarray['author-name'] = $contact['name'];
2520                                 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2521                                         $datarray['author-link'] = $contact['url'];
2522                                 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2523                                         $datarray['author-avatar'] = $contact['thumb'];
2524
2525                                 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2526                                         logger('consume_feed: no author information! ' . print_r($datarray,true));
2527                                         continue;
2528                                 }
2529
2530                                 $force_parent = false;
2531                                 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2532                                         if($contact['network'] === NETWORK_OSTATUS)
2533                                                 $force_parent = true;
2534                                         if(strlen($datarray['title']))
2535                                                 unset($datarray['title']);
2536                                         $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2537                                                 dbesc(datetime_convert()),
2538                                                 dbesc($parent_uri),
2539                                                 intval($importer['uid'])
2540                                         );
2541                                         $datarray['last-child'] = 1;
2542                                         update_thread_uri($parent_uri, $importer['uid']);
2543                                 }
2544
2545
2546                                 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2547                                         dbesc($item_id),
2548                                         intval($importer['uid'])
2549                                 );
2550
2551                                 // Update content if 'updated' changes
2552
2553                                 if(count($r)) {
2554                                         if (edited_timestamp_is_newer($r[0], $datarray)) {
2555
2556                                                 // do not accept (ignore) an earlier edit than one we currently have.
2557                                                 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2558                                                         continue;
2559
2560                                                 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2561                                                         dbesc($datarray['title']),
2562                                                         dbesc($datarray['body']),
2563                                                         dbesc($datarray['tag']),
2564                                                         dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2565                                                         dbesc(datetime_convert()),
2566                                                         dbesc($item_id),
2567                                                         intval($importer['uid'])
2568                                                 );
2569                                                 create_tags_from_itemuri($item_id, $importer['uid']);
2570                                                 update_thread_uri($item_id, $importer['uid']);
2571                                         }
2572
2573                                         // update last-child if it changes
2574
2575                                         $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2576                                         if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2577                                                 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2578                                                         dbesc(datetime_convert()),
2579                                                         dbesc($parent_uri),
2580                                                         intval($importer['uid'])
2581                                                 );
2582                                                 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s'  WHERE `uri` = '%s' AND `uid` = %d",
2583                                                         intval($allow[0]['data']),
2584                                                         dbesc(datetime_convert()),
2585                                                         dbesc($item_id),
2586                                                         intval($importer['uid'])
2587                                                 );
2588                                                 update_thread_uri($item_id, $importer['uid']);
2589                                         }
2590                                         continue;
2591                                 }
2592
2593
2594                                 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2595                                         // one way feed - no remote comment ability
2596                                         $datarray['last-child'] = 0;
2597                                 }
2598                                 $datarray['parent-uri'] = $parent_uri;
2599                                 $datarray['uid'] = $importer['uid'];
2600                                 $datarray['contact-id'] = $contact['id'];
2601                                 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2602                                         $datarray['type'] = 'activity';
2603                                         $datarray['gravity'] = GRAVITY_LIKE;
2604                                         // only one like or dislike per person
2605                                         // splitted into two queries for performance issues
2606                                         $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",
2607                                                 intval($datarray['uid']),
2608                                                 intval($datarray['contact-id']),
2609                                                 dbesc($datarray['verb']),
2610                                                 dbesc($parent_uri)
2611                                         );
2612                                         if($r && count($r))
2613                                                 continue;
2614
2615                                         $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",
2616                                                 intval($datarray['uid']),
2617                                                 intval($datarray['contact-id']),
2618                                                 dbesc($datarray['verb']),
2619                                                 dbesc($parent_uri)
2620                                         );
2621                                         if($r && count($r))
2622                                                 continue;
2623                                 }
2624
2625                                 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2626                                         $xo = parse_xml_string($datarray['object'],false);
2627                                         $xt = parse_xml_string($datarray['target'],false);
2628
2629                                         if($xt->type == ACTIVITY_OBJ_NOTE) {
2630                                                 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2631                                                         dbesc($xt->id),
2632                                                         intval($importer['importer_uid'])
2633                                                 );
2634                                                 if(! count($r))
2635                                                         continue;
2636
2637                                                 // extract tag, if not duplicate, add to parent item
2638                                                 if($xo->id && $xo->content) {
2639                                                         $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2640                                                         if(! (stristr($r[0]['tag'],$newtag))) {
2641                                                                 q("UPDATE item SET tag = '%s' WHERE id = %d",
2642                                                                         dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2643                                                                         intval($r[0]['id'])
2644                                                                 );
2645                                                                 create_tags_from_item($r[0]['id']);
2646                                                         }
2647                                                 }
2648                                         }
2649                                 }
2650
2651                                 $r = item_store($datarray,$force_parent);
2652                                 continue;
2653                         }
2654
2655                         else {
2656
2657                                 // Head post of a conversation. Have we seen it? If not, import it.
2658
2659                                 $item_id  = $item->get_id();
2660
2661                                 $datarray = get_atom_elements($feed, $item, $contact);
2662
2663                                 if(is_array($contact)) {
2664                                         if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2665                                                 $datarray['author-name'] = $contact['name'];
2666                                         if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2667                                                 $datarray['author-link'] = $contact['url'];
2668                                         if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2669                                                 $datarray['author-avatar'] = $contact['thumb'];
2670                                 }
2671
2672                                 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2673                                         logger('consume_feed: no author information! ' . print_r($datarray,true));
2674                                         continue;
2675                                 }
2676
2677                                 // special handling for events
2678
2679                                 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2680                                         $ev = bbtoevent($datarray['body']);
2681                                         if(x($ev,'desc') && x($ev,'start')) {
2682                                                 $ev['uid'] = $importer['uid'];
2683                                                 $ev['uri'] = $item_id;
2684                                                 $ev['edited'] = $datarray['edited'];
2685                                                 $ev['private'] = $datarray['private'];
2686
2687                                                 if(is_array($contact))
2688                                                         $ev['cid'] = $contact['id'];
2689                                                 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2690                                                         dbesc($item_id),
2691                                                         intval($importer['uid'])
2692                                                 );
2693                                                 if(count($r))
2694                                                         $ev['id'] = $r[0]['id'];
2695                                                 $xyz = event_store($ev);
2696                                                 continue;
2697                                         }
2698                                 }
2699
2700                                 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2701                                         if(strlen($datarray['title']))
2702                                                 unset($datarray['title']);
2703                                         $datarray['last-child'] = 1;
2704                                 }
2705
2706
2707                                 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2708                                         dbesc($item_id),
2709                                         intval($importer['uid'])
2710                                 );
2711
2712                                 // Update content if 'updated' changes
2713
2714                                 if(count($r)) {
2715                                         if (edited_timestamp_is_newer($r[0], $datarray)) {
2716
2717                                                 // do not accept (ignore) an earlier edit than one we currently have.
2718                                                 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2719                                                         continue;
2720
2721                                                 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2722                                                         dbesc($datarray['title']),
2723                                                         dbesc($datarray['body']),
2724                                                         dbesc($datarray['tag']),
2725                                                         dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2726                                                         dbesc(datetime_convert()),
2727                                                         dbesc($item_id),
2728                                                         intval($importer['uid'])
2729                                                 );
2730                                                 create_tags_from_itemuri($item_id, $importer['uid']);
2731                                                 update_thread_uri($item_id, $importer['uid']);
2732                                         }
2733
2734                                         // update last-child if it changes
2735
2736                                         $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2737                                         if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2738                                                 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2739                                                         intval($allow[0]['data']),
2740                                                         dbesc(datetime_convert()),
2741                                                         dbesc($item_id),
2742                                                         intval($importer['uid'])
2743                                                 );
2744                                                 update_thread_uri($item_id, $importer['uid']);
2745                                         }
2746                                         continue;
2747                                 }
2748
2749                                 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2750                                         logger('consume-feed: New follower');
2751                                         new_follower($importer,$contact,$datarray,$item);
2752                                         return;
2753                                 }
2754                                 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW))  {
2755                                         lose_follower($importer,$contact,$datarray,$item);
2756                                         return;
2757                                 }
2758
2759                                 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2760                                         logger('consume-feed: New friend request');
2761                                         new_follower($importer,$contact,$datarray,$item,true);
2762                                         return;
2763                                 }
2764                                 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND))  {
2765                                         lose_sharer($importer,$contact,$datarray,$item);
2766                                         return;
2767                                 }
2768
2769
2770                                 if(! is_array($contact))
2771                                         return;
2772
2773
2774                                 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2775                                                 // one way feed - no remote comment ability
2776                                                 $datarray['last-child'] = 0;
2777                                 }
2778                                 if($contact['network'] === NETWORK_FEED)
2779                                         $datarray['private'] = 2;
2780
2781                                 $datarray['parent-uri'] = $item_id;
2782                                 $datarray['uid'] = $importer['uid'];
2783                                 $datarray['contact-id'] = $contact['id'];
2784
2785                                 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2786                                         // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2787                                         // but otherwise there's a possible data mixup on the sender's system.
2788                                         // the tgroup delivery code called from item_store will correct it if it's a forum,
2789                                         // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2790                                         logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2791                                         $datarray['owner-name']   = $contact['name'];
2792                                         $datarray['owner-link']   = $contact['url'];
2793                                         $datarray['owner-avatar'] = $contact['thumb'];
2794                                 }
2795
2796                                 // We've allowed "followers" to reach this point so we can decide if they are
2797                                 // posting an @-tag delivery, which followers are allowed to do for certain
2798                                 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2799
2800                                 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2801                                         continue;
2802
2803                                 // This is my contact on another system, but it's really me.
2804                                 // Turn this into a wall post.
2805                                 $notify = item_is_remote_self($contact, $datarray);
2806
2807                                 $r = item_store($datarray, false, $notify);
2808                                 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2809                                 continue;
2810
2811                         }
2812                 }
2813         }
2814 }
2815
2816 function item_is_remote_self($contact, &$datarray) {
2817         $a = get_app();
2818
2819         if (!$contact['remote_self'])
2820                 return false;
2821
2822         // Prevent the forwarding of posts that are forwarded
2823         if ($datarray["extid"] == NETWORK_DFRN)
2824                 return false;
2825
2826         // Prevent to forward already forwarded posts
2827         if ($datarray["app"] == $a->get_hostname())
2828                 return false;
2829
2830         // Only forward posts
2831         if ($datarray["verb"] != ACTIVITY_POST)
2832                 return false;
2833
2834         if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2835                 return false;
2836
2837         $datarray2 = $datarray;
2838         logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2839         if ($contact['remote_self'] == 2) {
2840                 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2841                         intval($contact['uid']));
2842                 if (count($r)) {
2843                         $datarray['contact-id'] = $r[0]["id"];
2844
2845                         $datarray['owner-name'] = $r[0]["name"];
2846                         $datarray['owner-link'] = $r[0]["url"];
2847                         $datarray['owner-avatar'] = $r[0]["thumb"];
2848
2849                         $datarray['author-name']   = $datarray['owner-name'];
2850                         $datarray['author-link']   = $datarray['owner-link'];
2851                         $datarray['author-avatar'] = $datarray['owner-avatar'];
2852                 }
2853
2854                 if ($contact['network'] != NETWORK_FEED) {
2855                         $datarray["guid"] = get_guid(32);
2856                         unset($datarray["plink"]);
2857                         $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid']);
2858                         $datarray["parent-uri"] = $datarray["uri"];
2859                         $datarray["extid"] = $contact['network'];
2860                         $urlpart = parse_url($datarray2['author-link']);
2861                         $datarray["app"] = $urlpart["host"];
2862                 } else
2863                         $datarray['private'] = 0;
2864         }
2865
2866         //if (!isset($datarray["app"]) OR ($datarray["app"] == ""))
2867         //      $datarray["app"] = network_to_name($contact['network']);
2868
2869         if ($contact['network'] != NETWORK_FEED) {
2870                 // Store the original post
2871                 $r = item_store($datarray2, false, false);
2872                 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2873         } else
2874                 $datarray["app"] = "Feed";
2875
2876         return true;
2877 }
2878
2879 function local_delivery($importer,$data) {
2880         $a = get_app();
2881
2882         logger(__function__, LOGGER_TRACE);
2883
2884         if($importer['readonly']) {
2885                 // We aren't receiving stuff from this person. But we will quietly ignore them
2886                 // rather than a blatant "go away" message.
2887                 logger('local_delivery: ignoring');
2888                 return 0;
2889                 //NOTREACHED
2890         }
2891
2892         // Consume notification feed. This may differ from consuming a public feed in several ways
2893         // - might contain email or friend suggestions
2894         // - might contain remote followup to our message
2895         //              - in which case we need to accept it and then notify other conversants
2896         // - we may need to send various email notifications
2897
2898         $feed = new SimplePie();
2899         $feed->set_raw_data($data);
2900         $feed->enable_order_by_date(false);
2901         $feed->init();
2902
2903
2904         if($feed->error())
2905                 logger('local_delivery: Error parsing XML: ' . $feed->error());
2906
2907
2908         // Check at the feed level for updated contact name and/or photo
2909
2910         $name_updated  = '';
2911         $new_name = '';
2912         $photo_timestamp = '';
2913         $photo_url = '';
2914         $contact_updated = '';
2915
2916
2917         $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2918
2919 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2920 //      if(! $rawtags)
2921 //              $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2922
2923         if($rawtags) {
2924                 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2925                 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2926                         $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2927                         $new_name = $elems['name'][0]['data'];
2928
2929                         // Manually checking for changed contact names
2930                         if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
2931                                 $name_updated = date("c");
2932                                 $photo_timestamp = date("c");
2933                         }
2934                 }
2935                 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2936                         if ($photo_timestamp == "")
2937                                 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2938                         $photo_url = $elems['link'][0]['attribs']['']['href'];
2939                 }
2940         }
2941
2942         if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2943
2944                 $contact_updated = $photo_timestamp;
2945
2946                 logger('local_delivery: Updating photo for ' . $importer['name']);
2947                 require_once("include/Photo.php");
2948                 $photo_failure = false;
2949                 $have_photo = false;
2950
2951                 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2952                         intval($importer['id']),
2953                         intval($importer['importer_uid'])
2954                 );
2955                 if(count($r)) {
2956                         $resource_id = $r[0]['resource-id'];
2957                         $have_photo = true;
2958                 }
2959                 else {
2960                         $resource_id = photo_new_resource();
2961                 }
2962
2963                 $img_str = fetch_url($photo_url,true);
2964                 // guess mimetype from headers or filename
2965                 $type = guess_image_type($photo_url,true);
2966
2967
2968                 $img = new Photo($img_str, $type);
2969                 if($img->is_valid()) {
2970                         if($have_photo) {
2971                                 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2972                                         dbesc($resource_id),
2973                                         intval($importer['id']),
2974                                         intval($importer['importer_uid'])
2975                                 );
2976                         }
2977
2978                         $img->scaleImageSquare(175);
2979
2980                         $hash = $resource_id;
2981                         $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2982
2983                         $img->scaleImage(80);
2984                         $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2985
2986                         $img->scaleImage(48);
2987                         $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2988
2989                         $a = get_app();
2990
2991                         q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2992                                 WHERE `uid` = %d AND `id` = %d",
2993                                 dbesc(datetime_convert()),
2994                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2995                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2996                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2997                                 intval($importer['importer_uid']),
2998                                 intval($importer['id'])
2999                         );
3000                 }
3001         }
3002
3003         if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
3004                 if ($name_updated > $contact_updated)
3005                         $contact_updated = $name_updated;
3006
3007                 $r = q("select * from contact where uid = %d and id = %d limit 1",
3008                         intval($importer['importer_uid']),
3009                         intval($importer['id'])
3010                 );
3011
3012                 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
3013                         dbesc(notags(trim($new_name))),
3014                         dbesc(datetime_convert()),
3015                         intval($importer['importer_uid']),
3016                         intval($importer['id'])
3017                 );
3018
3019                 // do our best to update the name on content items
3020
3021                 if(count($r)) {
3022                         q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
3023                                 dbesc(notags(trim($new_name))),
3024                                 dbesc($r[0]['name']),
3025                                 dbesc($r[0]['url']),
3026                                 intval($importer['importer_uid'])
3027                         );
3028                 }
3029         }
3030
3031         if ($contact_updated AND $new_name AND $photo_url)
3032                 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3033
3034         // Currently unsupported - needs a lot of work
3035         $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3036         if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3037                 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3038                 $newloc = array();
3039                 $newloc['uid'] = $importer['importer_uid'];
3040                 $newloc['cid'] = $importer['id'];
3041                 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3042                 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3043                 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3044                 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3045                 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3046                 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3047                 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3048                 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3049                 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3050                 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3051                 /** relocated user must have original key pair */
3052                 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3053                 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3054
3055                 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3056
3057                 // update contact
3058                 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3059                         intval($importer['id']),
3060                         intval($importer['importer_uid']));
3061                 if ($r === false)
3062                         return 1;
3063                 $old = $r[0];
3064
3065                 $x = q("UPDATE contact SET
3066                                         name = '%s',
3067                                         photo = '%s',
3068                                         thumb = '%s',
3069                                         micro = '%s',
3070                                         url = '%s',
3071                                         nurl = '%s',
3072                                         request = '%s',
3073                                         confirm = '%s',
3074                                         notify = '%s',
3075                                         poll = '%s',
3076                                         `site-pubkey` = '%s'
3077                         WHERE id=%d AND uid=%d;",
3078                                         dbesc($newloc['name']),
3079                                         dbesc($newloc['photo']),
3080                                         dbesc($newloc['thumb']),
3081                                         dbesc($newloc['micro']),
3082                                         dbesc($newloc['url']),
3083                                         dbesc(normalise_link($newloc['url'])),
3084                                         dbesc($newloc['request']),
3085                                         dbesc($newloc['confirm']),
3086                                         dbesc($newloc['notify']),
3087                                         dbesc($newloc['poll']),
3088                                         dbesc($newloc['sitepubkey']),
3089                                         intval($importer['id']),
3090                                         intval($importer['importer_uid']));
3091
3092                 if ($x === false)
3093                         return 1;
3094                 // update items
3095                 $fields = array(
3096                         'owner-link' => array($old['url'], $newloc['url']),
3097                         'author-link' => array($old['url'], $newloc['url']),
3098                         'owner-avatar' => array($old['photo'], $newloc['photo']),
3099                         'author-avatar' => array($old['photo'], $newloc['photo']),
3100                         );
3101                 foreach ($fields as $n=>$f){
3102                         $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3103                                         $n, dbesc($f[1]),
3104                                         $n, dbesc($f[0]),
3105                                         intval($importer['importer_uid']));
3106                                 if ($x === false)
3107                                         return 1;
3108                         }
3109
3110                 // TODO
3111                 // merge with current record, current contents have priority
3112                 // update record, set url-updated
3113                 // update profile photos
3114                 // schedule a scan?
3115                 return 0;
3116         }
3117
3118
3119         // handle friend suggestion notification
3120
3121         $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3122         if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3123                 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3124                 $fsugg = array();
3125                 $fsugg['uid'] = $importer['importer_uid'];
3126                 $fsugg['cid'] = $importer['id'];
3127                 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3128                 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3129                 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3130                 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3131                 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3132
3133                 // Does our member already have a friend matching this description?
3134
3135                 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3136                         dbesc($fsugg['name']),
3137                         dbesc(normalise_link($fsugg['url'])),
3138                         intval($fsugg['uid'])
3139                 );
3140                 if(count($r))
3141                         return 0;
3142
3143                 // Do we already have an fcontact record for this person?
3144
3145                 $fid = 0;
3146                 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3147                         dbesc($fsugg['url']),
3148                         dbesc($fsugg['name']),
3149                         dbesc($fsugg['request'])
3150                 );
3151                 if(count($r)) {
3152                         $fid = $r[0]['id'];
3153
3154                         // OK, we do. Do we already have an introduction for this person ?
3155                         $r = q("select id from intro where uid = %d and fid = %d limit 1",
3156                                 intval($fsugg['uid']),
3157                                 intval($fid)
3158                         );
3159                         if(count($r))
3160                                 return 0;
3161                 }
3162                 if(! $fid)
3163                         $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3164                         dbesc($fsugg['name']),
3165                         dbesc($fsugg['url']),
3166                         dbesc($fsugg['photo']),
3167                         dbesc($fsugg['request'])
3168                 );
3169                 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3170                         dbesc($fsugg['url']),
3171                         dbesc($fsugg['name']),
3172                         dbesc($fsugg['request'])
3173                 );
3174                 if(count($r)) {
3175                         $fid = $r[0]['id'];
3176                 }
3177                 // database record did not get created. Quietly give up.
3178                 else
3179                         return 0;
3180
3181
3182                 $hash = random_string();
3183
3184                 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3185                         VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3186                         intval($fsugg['uid']),
3187                         intval($fid),
3188                         intval($fsugg['cid']),
3189                         dbesc($fsugg['body']),
3190                         dbesc($hash),
3191                         dbesc(datetime_convert()),
3192                         intval(0)
3193                 );
3194
3195                 notification(array(
3196                         'type'         => NOTIFY_SUGGEST,
3197                         'notify_flags' => $importer['notify-flags'],
3198                         'language'     => $importer['language'],
3199                         'to_name'      => $importer['username'],
3200                         'to_email'     => $importer['email'],
3201                         'uid'          => $importer['importer_uid'],
3202                         'item'         => $fsugg,
3203                         'link'         => $a->get_baseurl() . '/notifications/intros',
3204                         'source_name'  => $importer['name'],
3205                         'source_link'  => $importer['url'],
3206                         'source_photo' => $importer['photo'],
3207                         'verb'         => ACTIVITY_REQ_FRIEND,
3208                         'otype'        => 'intro'
3209                 ));
3210
3211                 return 0;
3212         }
3213
3214         $ismail = false;
3215
3216         $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3217         if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3218
3219                 logger('local_delivery: private message received');
3220
3221                 $ismail = true;
3222                 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3223
3224                 $msg = array();
3225                 $msg['uid'] = $importer['importer_uid'];
3226                 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3227                 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3228                 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3229                 $msg['contact-id'] = $importer['id'];
3230                 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3231                 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3232                 $msg['seen'] = 0;
3233                 $msg['replied'] = 0;
3234                 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3235                 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3236                 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3237
3238                 dbesc_array($msg);
3239
3240                 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3241                         . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3242
3243                 // send notifications.
3244
3245                 require_once('include/enotify.php');
3246
3247                 $notif_params = array(
3248                         'type' => NOTIFY_MAIL,
3249                         'notify_flags' => $importer['notify-flags'],
3250                         'language' => $importer['language'],
3251                         'to_name' => $importer['username'],
3252                         'to_email' => $importer['email'],
3253                         'uid' => $importer['importer_uid'],
3254                         'item' => $msg,
3255                         'source_name' => $msg['from-name'],
3256                         'source_link' => $importer['url'],
3257                         'source_photo' => $importer['thumb'],
3258                         'verb' => ACTIVITY_POST,
3259                         'otype' => 'mail'
3260                 );
3261
3262                 notification($notif_params);
3263                 return 0;
3264
3265                 // NOTREACHED
3266         }
3267
3268         $community_page = 0;
3269         $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3270         if($rawtags) {
3271                 $community_page = intval($rawtags[0]['data']);
3272         }
3273         if(intval($importer['forum']) != $community_page) {
3274                 q("update contact set forum = %d where id = %d",
3275                         intval($community_page),
3276                         intval($importer['id'])
3277                 );
3278                 $importer['forum'] = (string) $community_page;
3279         }
3280
3281         logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3282
3283         // process any deleted entries
3284
3285         $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3286         if(is_array($del_entries) && count($del_entries)) {
3287                 foreach($del_entries as $dentry) {
3288                         $deleted = false;
3289                         if(isset($dentry['attribs']['']['ref'])) {
3290                                 $uri = $dentry['attribs']['']['ref'];
3291                                 $deleted = true;
3292                                 if(isset($dentry['attribs']['']['when'])) {
3293                                         $when = $dentry['attribs']['']['when'];
3294                                         $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3295                                 }
3296                                 else
3297                                         $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3298                         }
3299                         if($deleted) {
3300
3301                                 // check for relayed deletes to our conversation
3302
3303                                 $is_reply = false;
3304                                 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3305                                         dbesc($uri),
3306                                         intval($importer['importer_uid'])
3307                                 );
3308                                 if(count($r)) {
3309                                         $parent_uri = $r[0]['parent-uri'];
3310                                         if($r[0]['id'] != $r[0]['parent'])
3311                                                 $is_reply = true;
3312                                 }
3313
3314                                 if($is_reply) {
3315                                         $community = false;
3316
3317                                         if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3318                                                 $sql_extra = '';
3319                                                 $community = true;
3320                                                 logger('local_delivery: possible community delete');
3321                                         }
3322                                         else
3323                                                 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3324
3325                                         // was the top-level post for this reply written by somebody on this site?
3326                                         // Specifically, the recipient?
3327
3328                                         $is_a_remote_delete = false;
3329
3330                                         // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3331                                         $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3332                                                 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3333                                                 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3334                                                 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3335                                                 AND `item`.`uid` = %d
3336                                                 $sql_extra
3337                                                 LIMIT 1",
3338                                                 dbesc($parent_uri),
3339                                                 dbesc($parent_uri),
3340                                                 dbesc($parent_uri),
3341                                                 intval($importer['importer_uid'])
3342                                         );
3343                                         if($r && count($r))
3344                                                 $is_a_remote_delete = true;
3345
3346                                         // Does this have the characteristics of a community or private group comment?
3347                                         // If it's a reply to a wall post on a community/prvgroup page it's a
3348                                         // valid community comment. Also forum_mode makes it valid for sure.
3349                                         // If neither, it's not.
3350
3351                                         if($is_a_remote_delete && $community) {
3352                                                 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3353                                                         $is_a_remote_delete = false;
3354                                                         logger('local_delivery: not a community delete');
3355                                                 }
3356                                         }
3357
3358                                         if($is_a_remote_delete) {
3359                                                 logger('local_delivery: received remote delete');
3360                                         }
3361                                 }
3362
3363                                 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3364                                         WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3365                                         dbesc($uri),
3366                                         intval($importer['importer_uid']),
3367                                         intval($importer['id'])
3368                                 );
3369
3370                                 if(count($r)) {
3371                                         $item = $r[0];
3372
3373                                         if($item['deleted'])
3374                                                 continue;
3375
3376                                         logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3377
3378                                         if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3379                                                 $xo = parse_xml_string($item['object'],false);
3380                                                 $xt = parse_xml_string($item['target'],false);
3381
3382                                                 if($xt->type === ACTIVITY_OBJ_NOTE) {
3383                                                         $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3384                                                                 dbesc($xt->id),
3385                                                                 intval($importer['importer_uid'])
3386                                                         );
3387                                                         if(count($i)) {
3388
3389                                                                 // For tags, the owner cannot remove the tag on the author's copy of the post.
3390
3391                                                                 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3392                                                                 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3393                                                                 $author_copy = (($item['origin']) ? true : false);
3394
3395                                                                 if($owner_remove && $author_copy)
3396                                                                         continue;
3397                                                                 if($author_remove || $owner_remove) {
3398                                                                         $tags = explode(',',$i[0]['tag']);
3399                                                                         $newtags = array();
3400                                                                         if(count($tags)) {
3401                                                                                 foreach($tags as $tag)
3402                                                                                         if(trim($tag) !== trim($xo->body))
3403                                                                                                 $newtags[] = trim($tag);
3404                                                                         }
3405                                                                         q("update item set tag = '%s' where id = %d",
3406                                                                                 dbesc(implode(',',$newtags)),
3407                                                                                 intval($i[0]['id'])
3408                                                                         );
3409                                                                         create_tags_from_item($i[0]['id']);
3410                                                                 }
3411                                                         }
3412                                                 }
3413                                         }
3414
3415                                         if($item['uri'] == $item['parent-uri']) {
3416                                                 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3417                                                         `body` = '', `title` = ''
3418                                                         WHERE `parent-uri` = '%s' AND `uid` = %d",
3419                                                         dbesc($when),
3420                                                         dbesc(datetime_convert()),
3421                                                         dbesc($item['uri']),
3422                                                         intval($importer['importer_uid'])
3423                                                 );
3424                                                 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3425                                                 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3426                                                 update_thread_uri($item['uri'], $importer['importer_uid']);
3427                                         }
3428                                         else {
3429                                                 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3430                                                         `body` = '', `title` = ''
3431                                                         WHERE `uri` = '%s' AND `uid` = %d",
3432                                                         dbesc($when),
3433                                                         dbesc(datetime_convert()),
3434                                                         dbesc($uri),
3435                                                         intval($importer['importer_uid'])
3436                                                 );
3437                                                 create_tags_from_itemuri($uri, $importer['importer_uid']);
3438                                                 create_files_from_itemuri($uri, $importer['importer_uid']);
3439                                                 update_thread_uri($uri, $importer['importer_uid']);
3440                                                 if($item['last-child']) {
3441                                                         // ensure that last-child is set in case the comment that had it just got wiped.
3442                                                         q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3443                                                                 dbesc(datetime_convert()),
3444                                                                 dbesc($item['parent-uri']),
3445                                                                 intval($item['uid'])
3446                                                         );
3447                                                         // who is the last child now?
3448                                                         $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3449                                                                 ORDER BY `created` DESC LIMIT 1",
3450                                                                         dbesc($item['parent-uri']),
3451                                                                         intval($importer['importer_uid'])
3452                                                         );
3453                                                         if(count($r)) {
3454                                                                 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3455                                                                         intval($r[0]['id'])
3456                                                                 );
3457                                                         }
3458                                                 }
3459                                                 // if this is a relayed delete, propagate it to other recipients
3460
3461                                                 if($is_a_remote_delete)
3462                                                         proc_run('php',"include/notifier.php","drop",$item['id']);
3463                                         }
3464                                 }
3465                         }
3466                 }
3467         }
3468
3469
3470         foreach($feed->get_items() as $item) {
3471
3472                 $is_reply = false;
3473                 $item_id = $item->get_id();
3474                 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3475                 if(isset($rawthread[0]['attribs']['']['ref'])) {
3476                         $is_reply = true;
3477                         $parent_uri = $rawthread[0]['attribs']['']['ref'];
3478                 }
3479
3480                 if($is_reply) {
3481                         $community = false;
3482
3483                         if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3484                                 $sql_extra = '';
3485                                 $community = true;
3486                                 logger('local_delivery: possible community reply');
3487                         }
3488                         else
3489                                 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3490
3491                         // was the top-level post for this reply written by somebody on this site?
3492                         // Specifically, the recipient?
3493
3494                         $is_a_remote_comment = false;
3495                         $top_uri = $parent_uri;
3496
3497                         $r = q("select `item`.`parent-uri` from `item`
3498                                 WHERE `item`.`uri` = '%s'
3499                                 LIMIT 1",
3500                                 dbesc($parent_uri)
3501                         );
3502                         if($r && count($r)) {
3503                                 $top_uri = $r[0]['parent-uri'];
3504
3505                                 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3506                                 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3507                                         `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3508                                         INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3509                                         WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3510                                         AND `item`.`uid` = %d
3511                                         $sql_extra
3512                                         LIMIT 1",
3513                                         dbesc($top_uri),
3514                                         dbesc($top_uri),
3515                                         dbesc($top_uri),
3516                                         intval($importer['importer_uid'])
3517                                 );
3518                                 if($r && count($r))
3519                                         $is_a_remote_comment = true;
3520                         }
3521
3522                         // Does this have the characteristics of a community or private group comment?
3523                         // If it's a reply to a wall post on a community/prvgroup page it's a
3524                         // valid community comment. Also forum_mode makes it valid for sure.
3525                         // If neither, it's not.
3526
3527                         if($is_a_remote_comment && $community) {
3528                                 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3529                                         $is_a_remote_comment = false;
3530                                         logger('local_delivery: not a community reply');
3531                                 }
3532                         }
3533
3534                         if($is_a_remote_comment) {
3535                                 logger('local_delivery: received remote comment');
3536                                 $is_like = false;
3537                                 // remote reply to our post. Import and then notify everybody else.
3538
3539                                 $datarray = get_atom_elements($feed, $item);
3540
3541                                 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body`  FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3542                                         dbesc($item_id),
3543                                         intval($importer['importer_uid'])
3544                                 );
3545
3546                                 // Update content if 'updated' changes
3547
3548                                 if(count($r)) {
3549                                         $iid = $r[0]['id'];
3550                                         if (edited_timestamp_is_newer($r[0], $datarray)) {
3551
3552                                                 // do not accept (ignore) an earlier edit than one we currently have.
3553                                                 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3554                                                         continue;
3555
3556                                                 logger('received updated comment' , LOGGER_DEBUG);
3557                                                 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3558                                                         dbesc($datarray['title']),
3559                                                         dbesc($datarray['body']),
3560                                                         dbesc($datarray['tag']),
3561                                                         dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3562                                                         dbesc(datetime_convert()),
3563                                                         dbesc($item_id),
3564                                                         intval($importer['importer_uid'])
3565                                                 );
3566                                                 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3567
3568                                                 proc_run('php',"include/notifier.php","comment-import",$iid);
3569
3570                                         }
3571
3572                                         continue;
3573                                 }
3574
3575
3576
3577                                 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3578                                         intval($importer['importer_uid'])
3579                                 );
3580
3581
3582                                 $datarray['type'] = 'remote-comment';
3583                                 $datarray['wall'] = 1;
3584                                 $datarray['parent-uri'] = $parent_uri;
3585                                 $datarray['uid'] = $importer['importer_uid'];
3586                                 $datarray['owner-name'] = $own[0]['name'];
3587                                 $datarray['owner-link'] = $own[0]['url'];
3588                                 $datarray['owner-avatar'] = $own[0]['thumb'];
3589                                 $datarray['contact-id'] = $importer['id'];
3590
3591                                 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3592                                         $is_like = true;
3593                                         $datarray['type'] = 'activity';
3594                                         $datarray['gravity'] = GRAVITY_LIKE;
3595                                         $datarray['last-child'] = 0;
3596                                         // only one like or dislike per person
3597                                         // splitted into two queries for performance issues
3598                                         $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",
3599                                                 intval($datarray['uid']),
3600                                                 intval($datarray['contact-id']),
3601                                                 dbesc($datarray['verb']),
3602                                                 dbesc($datarray['parent-uri'])
3603
3604                                         );
3605                                         if($r && count($r))
3606                                                 continue;
3607
3608                                         $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",
3609                                                 intval($datarray['uid']),
3610                                                 intval($datarray['contact-id']),
3611                                                 dbesc($datarray['verb']),
3612                                                 dbesc($datarray['parent-uri'])
3613
3614                                         );
3615                                         if($r && count($r))
3616                                                 continue;
3617                                 }
3618
3619                                 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3620
3621                                         $xo = parse_xml_string($datarray['object'],false);
3622                                         $xt = parse_xml_string($datarray['target'],false);
3623
3624                                         if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3625
3626                                                 // fetch the parent item
3627
3628                                                 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3629                                                         dbesc($xt->id),
3630                                                         intval($importer['importer_uid'])
3631                                                 );
3632                                                 if(! count($tagp))
3633                                                         continue;
3634
3635                                                 // extract tag, if not duplicate, and this user allows tags, add to parent item
3636
3637                                                 if($xo->id && $xo->content) {
3638                                                         $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3639                                                         if(! (stristr($tagp[0]['tag'],$newtag))) {
3640                                                                 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3641                                                                         intval($importer['importer_uid'])
3642                                                                 );
3643                                                                 if(count($i) && ! intval($i[0]['blocktags'])) {
3644                                                                         q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3645                                                                                 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3646                                                                                 intval($tagp[0]['id']),
3647                                                                                 dbesc(datetime_convert()),
3648                                                                                 dbesc(datetime_convert())
3649                                                                         );
3650                                                                         create_tags_from_item($tagp[0]['id']);
3651                                                                 }
3652                                                         }
3653                                                 }
3654                                         }
3655                                 }
3656
3657
3658                                 $posted_id = item_store($datarray);
3659                                 $parent = 0;
3660
3661                                 if($posted_id) {
3662                                         $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3663                                                 intval($posted_id),
3664                                                 intval($importer['importer_uid'])
3665                                         );
3666                                         if(count($r)) {
3667                                                 $parent = $r[0]['parent'];
3668                                                 $parent_uri = $r[0]['parent-uri'];
3669                                         }
3670
3671                                         if(! $is_like) {
3672                                                 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3673                                                         dbesc(datetime_convert()),
3674                                                         intval($importer['importer_uid']),
3675                                                         intval($r[0]['parent'])
3676                                                 );
3677
3678                                                 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3679                                                         dbesc(datetime_convert()),
3680                                                         intval($importer['importer_uid']),
3681                                                         intval($posted_id)
3682                                                 );
3683                                         }
3684
3685                                         if($posted_id && $parent) {
3686
3687                                                 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3688
3689                                                 if((! $is_like) && (! $importer['self'])) {
3690
3691                                                         require_once('include/enotify.php');
3692
3693                                                         notification(array(
3694                                                                 'type'         => NOTIFY_COMMENT,
3695                                                                 'notify_flags' => $importer['notify-flags'],
3696                                                                 'language'     => $importer['language'],
3697                                                                 'to_name'      => $importer['username'],
3698                                                                 'to_email'     => $importer['email'],
3699                                                                 'uid'          => $importer['importer_uid'],
3700                                                                 'item'         => $datarray,
3701                                                                 'link'             => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3702                                                                 'source_name'  => stripslashes($datarray['author-name']),
3703                                                                 'source_link'  => $datarray['author-link'],
3704                                                                 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3705                                                                         ? $importer['thumb'] : $datarray['author-avatar']),
3706                                                                 'verb'         => ACTIVITY_POST,
3707                                                                 'otype'        => 'item',
3708                                                                 'parent'       => $parent,
3709                                                                 'parent_uri'   => $parent_uri,
3710                                                         ));
3711
3712                                                 }
3713                                         }
3714
3715                                         return 0;
3716                                         // NOTREACHED
3717                                 }
3718                         }
3719                         else {
3720
3721                                 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3722
3723                                 $item_id  = $item->get_id();
3724                                 $datarray = get_atom_elements($feed,$item);
3725
3726                                 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3727                                         continue;
3728
3729                                 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3730                                         dbesc($item_id),
3731                                         intval($importer['importer_uid'])
3732                                 );
3733
3734                                 // Update content if 'updated' changes
3735
3736                                 if(count($r)) {
3737                                         if (edited_timestamp_is_newer($r[0], $datarray)) {
3738
3739                                                 // do not accept (ignore) an earlier edit than one we currently have.
3740                                                 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3741                                                         continue;
3742
3743                                                 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3744                                                         dbesc($datarray['title']),
3745                                                         dbesc($datarray['body']),
3746                                                         dbesc($datarray['tag']),
3747                                                         dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3748                                                         dbesc(datetime_convert()),
3749                                                         dbesc($item_id),
3750                                                         intval($importer['importer_uid'])
3751                                                 );
3752                                                 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3753                                         }
3754
3755                                         // update last-child if it changes
3756
3757                                         $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3758                                         if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3759                                                 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3760                                                         dbesc(datetime_convert()),
3761                                                         dbesc($parent_uri),
3762                                                         intval($importer['importer_uid'])
3763                                                 );
3764                                                 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s'  WHERE `uri` = '%s' AND `uid` = %d",
3765                                                         intval($allow[0]['data']),
3766                                                         dbesc(datetime_convert()),
3767                                                         dbesc($item_id),
3768                                                         intval($importer['importer_uid'])
3769                                                 );
3770                                         }
3771                                         continue;
3772                                 }
3773
3774                                 $datarray['parent-uri'] = $parent_uri;
3775                                 $datarray['uid'] = $importer['importer_uid'];
3776                                 $datarray['contact-id'] = $importer['id'];
3777                                 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3778                                         $datarray['type'] = 'activity';
3779                                         $datarray['gravity'] = GRAVITY_LIKE;
3780                                         // only one like or dislike per person
3781                                         // splitted into two queries for performance issues
3782                                         $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",
3783                                                 intval($datarray['uid']),
3784                                                 intval($datarray['contact-id']),
3785                                                 dbesc($datarray['verb']),
3786                                                 dbesc($parent_uri)
3787                                         );
3788                                         if($r && count($r))
3789                                                 continue;
3790
3791                                         $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",
3792                                                 intval($datarray['uid']),
3793                                                 intval($datarray['contact-id']),
3794                                                 dbesc($datarray['verb']),
3795                                                 dbesc($parent_uri)
3796                                         );
3797                                         if($r && count($r))
3798                                                 continue;
3799
3800                                 }
3801
3802                                 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3803
3804                                         $xo = parse_xml_string($datarray['object'],false);
3805                                         $xt = parse_xml_string($datarray['target'],false);
3806
3807                                         if($xt->type == ACTIVITY_OBJ_NOTE) {
3808                                                 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3809                                                         dbesc($xt->id),
3810                                                         intval($importer['importer_uid'])
3811                                                 );
3812                                                 if(! count($r))
3813                                                         continue;
3814
3815                                                 // extract tag, if not duplicate, add to parent item
3816                                                 if($xo->content) {
3817                                                         if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3818                                                                 q("UPDATE item SET tag = '%s' WHERE id = %d",
3819                                                                         dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3820                                                                         intval($r[0]['id'])
3821                                                                 );
3822                                                                 create_tags_from_item($r[0]['id']);
3823                                                         }
3824                                                 }
3825                                         }
3826                                 }
3827
3828                                 $posted_id = item_store($datarray);
3829
3830                                 // find out if our user is involved in this conversation and wants to be notified.
3831
3832                                 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3833
3834                                         $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3835                                                 dbesc($top_uri),
3836                                                 intval($importer['importer_uid'])
3837                                         );
3838
3839                                         if(count($myconv)) {
3840                                                 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3841
3842                                                 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3843                                                 if(! link_compare($datarray['author-link'],$importer_url)) {
3844
3845
3846                                                         foreach($myconv as $conv) {
3847
3848                                                                 // now if we find a match, it means we're in this conversation
3849
3850                                                                 if(! link_compare($conv['author-link'],$importer_url))
3851                                                                         continue;
3852
3853                                                                 require_once('include/enotify.php');
3854
3855                                                                 $conv_parent = $conv['parent'];
3856
3857                                                                 notification(array(
3858                                                                         'type'         => NOTIFY_COMMENT,
3859                                                                         'notify_flags' => $importer['notify-flags'],
3860                                                                         'language'     => $importer['language'],
3861                                                                         'to_name'      => $importer['username'],
3862                                                                         'to_email'     => $importer['email'],
3863                                                                         'uid'          => $importer['importer_uid'],
3864                                                                         'item'         => $datarray,
3865                                                                         'link'             => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3866                                                                         'source_name'  => stripslashes($datarray['author-name']),
3867                                                                         'source_link'  => $datarray['author-link'],
3868                                                                         'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3869                                                                                 ? $importer['thumb'] : $datarray['author-avatar']),
3870                                                                         'verb'         => ACTIVITY_POST,
3871                                                                         'otype'        => 'item',
3872                                                                         'parent'       => $conv_parent,
3873                                                                         'parent_uri'   => $parent_uri
3874
3875                                                                 ));
3876
3877                                                                 // only send one notification
3878                                                                 break;
3879                                                         }
3880                                                 }
3881                                         }
3882                                 }
3883                                 continue;
3884                         }
3885                 }
3886
3887                 else {
3888
3889                         // Head post of a conversation. Have we seen it? If not, import it.
3890
3891
3892                         $item_id  = $item->get_id();
3893                         $datarray = get_atom_elements($feed,$item);
3894
3895                         if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3896                                 $ev = bbtoevent($datarray['body']);
3897                                 if(x($ev,'desc') && x($ev,'start')) {
3898                                         $ev['cid'] = $importer['id'];
3899                                         $ev['uid'] = $importer['uid'];
3900                                         $ev['uri'] = $item_id;
3901                                         $ev['edited'] = $datarray['edited'];
3902                                         $ev['private'] = $datarray['private'];
3903
3904                                         $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3905                                                 dbesc($item_id),
3906                                                 intval($importer['uid'])
3907                                         );
3908                                         if(count($r))
3909                                                 $ev['id'] = $r[0]['id'];
3910                                         $xyz = event_store($ev);
3911                                         continue;
3912                                 }
3913                         }
3914
3915                         $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3916                                 dbesc($item_id),
3917                                 intval($importer['importer_uid'])
3918                         );
3919
3920                         // Update content if 'updated' changes
3921
3922                         if(count($r)) {
3923                                 if (edited_timestamp_is_newer($r[0], $datarray)) {
3924
3925                                         // do not accept (ignore) an earlier edit than one we currently have.
3926                                         if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3927                                                 continue;
3928
3929                                         $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3930                                                 dbesc($datarray['title']),
3931                                                 dbesc($datarray['body']),
3932                                                 dbesc($datarray['tag']),
3933                                                 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3934                                                 dbesc(datetime_convert()),
3935                                                 dbesc($item_id),
3936                                                 intval($importer['importer_uid'])
3937                                         );
3938                                         create_tags_from_itemuri($item_id, $importer['importer_uid']);
3939                                         update_thread_uri($item_id, $importer['importer_uid']);
3940                                 }
3941
3942                                 // update last-child if it changes
3943
3944                                 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3945                                 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3946                                         $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3947                                                 intval($allow[0]['data']),
3948                                                 dbesc(datetime_convert()),
3949                                                 dbesc($item_id),
3950                                                 intval($importer['importer_uid'])
3951                                         );
3952                                 }
3953                                 continue;
3954                         }
3955
3956                         $datarray['parent-uri'] = $item_id;
3957                         $datarray['uid'] = $importer['importer_uid'];
3958                         $datarray['contact-id'] = $importer['id'];
3959
3960
3961                         if(! link_compare($datarray['owner-link'],$importer['url'])) {
3962                                 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3963                                 // but otherwise there's a possible data mixup on the sender's system.
3964                                 // the tgroup delivery code called from item_store will correct it if it's a forum,
3965                                 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3966                                 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3967                                 $datarray['owner-name']   = $importer['senderName'];
3968                                 $datarray['owner-link']   = $importer['url'];
3969                                 $datarray['owner-avatar'] = $importer['thumb'];
3970                         }
3971
3972                         if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3973                                 continue;
3974
3975                         // This is my contact on another system, but it's really me.
3976                         // Turn this into a wall post.
3977                         $notify = item_is_remote_self($importer, $datarray);
3978
3979                         $posted_id = item_store($datarray, false, $notify);
3980
3981                         if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3982                                 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3983                                 if(! $verb)
3984                                         continue;
3985                                 $xo = parse_xml_string($datarray['object'],false);
3986
3987                                 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3988
3989                                         // somebody was poked/prodded. Was it me?
3990
3991                                         $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3992
3993                                 foreach($links->link as $l) {
3994                                 $atts = $l->attributes();
3995                                 switch($atts['rel']) {
3996                                         case "alternate":
3997                                                                 $Blink = $atts['href'];
3998                                                                 break;
3999                                                         default:
4000                                                                 break;
4001                                     }
4002                                 }
4003                                         if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
4004
4005                                                 // send a notification
4006                                                 require_once('include/enotify.php');
4007
4008                                                 notification(array(
4009                                                         'type'         => NOTIFY_POKE,
4010                                                         'notify_flags' => $importer['notify-flags'],
4011                                                         'language'     => $importer['language'],
4012                                                         'to_name'      => $importer['username'],
4013                                                         'to_email'     => $importer['email'],
4014                                                         'uid'          => $importer['importer_uid'],
4015                                                         'item'         => $datarray,
4016                                                         'link'             => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4017                                                         'source_name'  => stripslashes($datarray['author-name']),
4018                                                         'source_link'  => $datarray['author-link'],
4019                                                         'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4020                                                                 ? $importer['thumb'] : $datarray['author-avatar']),
4021                                                         'verb'         => $datarray['verb'],
4022                                                         'otype'        => 'person',
4023                                                         'activity'     => $verb,
4024
4025                                                 ));
4026                                         }
4027                                 }
4028                         }
4029
4030                         continue;
4031                 }
4032         }
4033
4034         return 0;
4035         // NOTREACHED
4036
4037 }
4038
4039
4040 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4041         $url = notags(trim($datarray['author-link']));
4042         $name = notags(trim($datarray['author-name']));
4043         $photo = notags(trim($datarray['author-avatar']));
4044
4045         $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4046         if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4047                 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4048
4049         if(is_array($contact)) {
4050                 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4051                         || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4052                         $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4053                                 intval(CONTACT_IS_FRIEND),
4054                                 intval($contact['id']),
4055                                 intval($importer['uid'])
4056                         );
4057                 }
4058                 // send email notification to owner?
4059         }
4060         else {
4061
4062                 // create contact record
4063
4064                 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4065                         `blocked`, `readonly`, `pending`, `writable` )
4066                         VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4067                         intval($importer['uid']),
4068                         dbesc(datetime_convert()),
4069                         dbesc($url),
4070                         dbesc(normalise_link($url)),
4071                         dbesc($name),
4072                         dbesc($nick),
4073                         dbesc($photo),
4074                         dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4075                         intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4076                 );
4077                 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4078                                 intval($importer['uid']),
4079                                 dbesc($url)
4080                 );
4081                 if(count($r))
4082                                 $contact_record = $r[0];
4083
4084                 // create notification
4085                 $hash = random_string();
4086
4087                 if(is_array($contact_record)) {
4088                         $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4089                                 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4090                                 intval($importer['uid']),
4091                                 intval($contact_record['id']),
4092                                 dbesc($hash),
4093                                 dbesc(datetime_convert())
4094                         );
4095                 }
4096
4097                 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4098                         intval($importer['uid'])
4099                 );
4100                 $a = get_app();
4101                 if(count($r)) {
4102
4103                         if(intval($r[0]['def_gid'])) {
4104                                 require_once('include/group.php');
4105                                 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4106                         }
4107
4108                         if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4109                                 (($r[0]['page-flags'] == PAGE_NORMAL) OR ($r[0]['page-flags'] == PAGE_SOAPBOX))) {
4110
4111
4112
4113                                 notification(array(
4114                                         'type'         => NOTIFY_INTRO,
4115                                         'notify_flags' => $r[0]['notify-flags'],
4116                                         'language'     => $r[0]['language'],
4117                                         'to_name'      => $r[0]['username'],
4118                                         'to_email'     => $r[0]['email'],
4119                                         'uid'          => $r[0]['uid'],
4120                                         'link'             => $a->get_baseurl() . '/notifications/intro',
4121                                         'source_name'  => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4122                                         'source_link'  => $contact_record['url'],
4123                                         'source_photo' => $contact_record['photo'],
4124                                         'verb'         => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4125                                         'otype'        => 'intro'
4126                                 ));
4127
4128
4129                         }
4130                 }
4131         }
4132 }
4133
4134 function lose_follower($importer,$contact,$datarray,$item) {
4135
4136         if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4137                 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4138                         intval(CONTACT_IS_SHARING),
4139                         intval($contact['id'])
4140                 );
4141         }
4142         else {
4143                 contact_remove($contact['id']);
4144         }
4145 }
4146
4147 function lose_sharer($importer,$contact,$datarray,$item) {
4148
4149         if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4150                 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4151                         intval(CONTACT_IS_FOLLOWER),
4152                         intval($contact['id'])
4153                 );
4154         }
4155         else {
4156                 contact_remove($contact['id']);
4157         }
4158 }
4159
4160
4161 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4162
4163         $a = get_app();
4164
4165         if(is_array($importer)) {
4166                 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4167                         intval($importer['uid'])
4168                 );
4169         }
4170
4171         // Diaspora has different message-ids in feeds than they do
4172         // through the direct Diaspora protocol. If we try and use
4173         // the feed, we'll get duplicates. So don't.
4174
4175         if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4176                 return;
4177
4178         $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4179
4180         // Use a single verify token, even if multiple hubs
4181
4182         $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4183
4184         $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4185
4186         logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: '  . $push_url . ' with verifier ' . $verify_token);
4187
4188         if(! strlen($contact['hub-verify'])) {
4189                 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4190                         dbesc($verify_token),
4191                         intval($contact['id'])
4192                 );
4193         }
4194
4195         post_url($url,$params);
4196
4197         logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4198
4199         return;
4200
4201 }
4202
4203
4204 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4205         $o = '';
4206         if(! $tag)
4207                 return $o;
4208         $name = xmlify($name);
4209         $uri = xmlify($uri);
4210         $h = intval($h);
4211         $w = intval($w);
4212         $photo = xmlify($photo);
4213
4214
4215         $o .= "<$tag>\r\n";
4216         $o .= "<name>$name</name>\r\n";
4217         $o .= "<uri>$uri</uri>\r\n";
4218         $o .= '<link rel="photo"  type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4219         $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4220
4221         call_hooks('atom_author', $o);
4222
4223         $o .= "</$tag>\r\n";
4224         return $o;
4225 }
4226
4227 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4228
4229         $a = get_app();
4230
4231         if(! $item['parent'])
4232                 return;
4233
4234         if($item['deleted'])
4235                 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4236
4237
4238         if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4239                 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4240         else
4241                 $body = $item['body'];
4242
4243
4244         $o = "\r\n\r\n<entry>\r\n";
4245
4246         if(is_array($author))
4247                 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4248         else
4249                 $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']));
4250         if(strlen($item['owner-name']))
4251                 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4252
4253         if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4254                 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4255                 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' .  xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
4256         }
4257
4258         $htmlbody = $body;
4259
4260         if ($item['title'] != "")
4261                 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4262
4263         $htmlbody = bbcode(bb_remove_share_information($htmlbody), false, false, 7);
4264
4265         $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4266         $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4267         $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4268         $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4269         $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4270         $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4271         $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
4272
4273
4274         if($comment)
4275                 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4276
4277         if($item['location']) {
4278                 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4279                 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4280         }
4281
4282         if($item['coord'])
4283                 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4284
4285         if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4286                 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4287
4288         if($item['extid'])
4289                 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4290         if($item['bookmark'])
4291                 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4292
4293         if($item['app'])
4294                 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4295
4296         if($item['guid'])
4297                 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4298
4299         if($item['signed_text']) {
4300                 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4301                 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4302         }
4303
4304         $verb = construct_verb($item);
4305         $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4306         $actobj = construct_activity_object($item);
4307         if(strlen($actobj))
4308                 $o .= $actobj;
4309         $actarg = construct_activity_target($item);
4310         if(strlen($actarg))
4311                 $o .= $actarg;
4312
4313         $tags = item_getfeedtags($item);
4314         if(count($tags)) {
4315                 foreach($tags as $t) {
4316                         $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4317                 }
4318         }
4319
4320         $o .= item_getfeedattach($item);
4321
4322         $mentioned = get_mentions($item);
4323         if($mentioned)
4324                 $o .= $mentioned;
4325
4326         call_hooks('atom_entry', $o);
4327
4328         $o .= '</entry>' . "\r\n";
4329
4330         return $o;
4331 }
4332
4333 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4334
4335         if(get_config('system','disable_embedded'))
4336                 return $s;
4337
4338         $a = get_app();
4339
4340         logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4341         $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4342
4343         $orig_body = $s;
4344         $new_body = '';
4345
4346         $img_start = strpos($orig_body, '[img');
4347         $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4348         $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4349         while( ($img_st_close !== false) && ($img_len !== false) ) {
4350
4351                 $img_st_close++; // make it point to AFTER the closing bracket
4352                 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4353
4354                 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4355
4356
4357                 if(stristr($image , $site . '/photo/')) {
4358                         // Only embed locally hosted photos
4359                         $replace = false;
4360                         $i = basename($image);
4361                         $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4362                         $x = strpos($i,'-');
4363
4364                         if($x) {
4365                                 $res = substr($i,$x+1);
4366                                 $i = substr($i,0,$x);
4367                                 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4368                                         dbesc($i),
4369                                         intval($res),
4370                                         intval($uid)
4371                                 );
4372                                 if($r) {
4373
4374                                         // Check to see if we should replace this photo link with an embedded image
4375                                         // 1. No need to do so if the photo is public
4376                                         // 2. If there's a contact-id provided, see if they're in the access list
4377                                         //    for the photo. If so, embed it.
4378                                         // 3. Otherwise, if we have an item, see if the item permissions match the photo
4379                                         //    permissions, regardless of order but first check to see if they're an exact
4380                                         //    match to save some processing overhead.
4381
4382                                         if(has_permissions($r[0])) {
4383                                                 if($cid) {
4384                                                         $recips = enumerate_permissions($r[0]);
4385                                                         if(in_array($cid, $recips)) {
4386                                                                 $replace = true;
4387                                                         }
4388                                                 }
4389                                                 elseif($item) {
4390                                                         if(compare_permissions($item,$r[0]))
4391                                                                 $replace = true;
4392                                                 }
4393                                         }
4394                                         if($replace) {
4395                                                 $data = $r[0]['data'];
4396                                                 $type = $r[0]['type'];
4397
4398                                                 // If a custom width and height were specified, apply before embedding
4399                                                 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4400                                                         logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4401
4402                                                         $width = intval($match[1]);
4403                                                         $height = intval($match[2]);
4404
4405                                                         $ph = new Photo($data, $type);
4406                                                         if($ph->is_valid()) {
4407                                                                 $ph->scaleImage(max($width, $height));
4408                                                                 $data = $ph->imageString();
4409                                                                 $type = $ph->getType();
4410                                                         }
4411                                                 }
4412
4413                                                 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4414                                                 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4415                                                 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4416                                         }
4417                                 }
4418                         }
4419                 }
4420
4421                 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4422                 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4423                 if($orig_body === false)
4424                         $orig_body = '';
4425
4426                 $img_start = strpos($orig_body, '[img');
4427                 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4428                 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4429         }
4430
4431         $new_body = $new_body . $orig_body;
4432
4433         return($new_body);
4434 }
4435
4436
4437 function has_permissions($obj) {
4438         if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4439                 return true;
4440         return false;
4441 }
4442
4443 function compare_permissions($obj1,$obj2) {
4444         // first part is easy. Check that these are exactly the same.
4445         if(($obj1['allow_cid'] == $obj2['allow_cid'])
4446                 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4447                 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4448                 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4449                 return true;
4450
4451         // This is harder. Parse all the permissions and compare the resulting set.
4452
4453         $recipients1 = enumerate_permissions($obj1);
4454         $recipients2 = enumerate_permissions($obj2);
4455         sort($recipients1);
4456         sort($recipients2);
4457         if($recipients1 == $recipients2)
4458                 return true;
4459         return false;
4460 }
4461
4462 // returns an array of contact-ids that are allowed to see this object
4463
4464 function enumerate_permissions($obj) {
4465         require_once('include/group.php');
4466         $allow_people = expand_acl($obj['allow_cid']);
4467         $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4468         $deny_people  = expand_acl($obj['deny_cid']);
4469         $deny_groups  = expand_groups(expand_acl($obj['deny_gid']));
4470         $recipients   = array_unique(array_merge($allow_people,$allow_groups));
4471         $deny         = array_unique(array_merge($deny_people,$deny_groups));
4472         $recipients   = array_diff($recipients,$deny);
4473         return $recipients;
4474 }
4475
4476 function item_getfeedtags($item) {
4477         $ret = array();
4478         $matches = false;
4479         $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4480         if($cnt) {
4481                 for($x = 0; $x < $cnt; $x ++) {
4482                         if($matches[1][$x])
4483                                 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4484                 }
4485         }
4486         $matches = false;
4487         $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4488         if($cnt) {
4489                 for($x = 0; $x < $cnt; $x ++) {
4490                         if($matches[1][$x])
4491                                 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4492                 }
4493         }
4494         return $ret;
4495 }
4496
4497 function item_getfeedattach($item) {
4498         $ret = '';
4499         $arr = explode('[/attach],',$item['attach']);
4500         if(count($arr)) {
4501                 foreach($arr as $r) {
4502                         $matches = false;
4503                         $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4504                         if($cnt) {
4505                                 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4506                                 if(intval($matches[2]))
4507                                         $ret .= 'length="' . intval($matches[2]) . '" ';
4508                                 if($matches[4] !== ' ')
4509                                         $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4510                                 $ret .= ' />' . "\r\n";
4511                         }
4512                 }
4513         }
4514         return $ret;
4515 }
4516
4517
4518
4519 function item_expire($uid, $days, $network = "", $force = false) {
4520
4521         if((! $uid) || ($days < 1))
4522                 return;
4523
4524         // $expire_network_only = save your own wall posts
4525         // and just expire conversations started by others
4526
4527         $expire_network_only = get_pconfig($uid,'expire','network_only');
4528         $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4529
4530         if ($network != "") {
4531                 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4532                 // There is an index "uid_network_received" but not "uid_network_created"
4533                 // This avoids the creation of another index just for one purpose.
4534                 // And it doesn't really matter wether to look at "received" or "created"
4535                 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4536         } else
4537                 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4538
4539         $r = q("SELECT * FROM `item`
4540                 WHERE `uid` = %d $range
4541                 AND `id` = `parent`
4542                 $sql_extra
4543                 AND `deleted` = 0",
4544                 intval($uid),
4545                 intval($days)
4546         );
4547
4548         if(! count($r))
4549                 return;
4550
4551         $expire_items = get_pconfig($uid, 'expire','items');
4552         $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4553
4554         // Forcing expiring of items - but not notes and marked items
4555         if ($force)
4556                 $expire_items = true;
4557
4558         $expire_notes = get_pconfig($uid, 'expire','notes');
4559         $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4560
4561         $expire_starred = get_pconfig($uid, 'expire','starred');
4562         $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4563
4564         $expire_photos = get_pconfig($uid, 'expire','photos');
4565         $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4566
4567         logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4568
4569         foreach($r as $item) {
4570
4571                 // don't expire filed items
4572
4573                 if(strpos($item['file'],'[') !== false)
4574                         continue;
4575
4576                 // Only expire posts, not photos and photo comments
4577
4578                 if($expire_photos==0 && strlen($item['resource-id']))
4579                         continue;
4580                 if($expire_starred==0 && intval($item['starred']))
4581                         continue;
4582                 if($expire_notes==0 && $item['type']=='note')
4583                         continue;
4584                 if($expire_items==0 && $item['type']!='note')
4585                         continue;
4586
4587                 drop_item($item['id'],false);
4588         }
4589
4590         proc_run('php',"include/notifier.php","expire","$uid");
4591
4592 }
4593
4594
4595 function drop_items($items) {
4596         $uid = 0;
4597
4598         if(! local_user() && ! remote_user())
4599                 return;
4600
4601         if(count($items)) {
4602                 foreach($items as $item) {
4603                         $owner = drop_item($item,false);
4604                         if($owner && ! $uid)
4605                                 $uid = $owner;
4606                 }
4607         }
4608
4609         // multiple threads may have been deleted, send an expire notification
4610
4611         if($uid)
4612                 proc_run('php',"include/notifier.php","expire","$uid");
4613 }
4614
4615
4616 function drop_item($id,$interactive = true) {
4617
4618         $a = get_app();
4619
4620         // locate item to be deleted
4621
4622         $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4623                 intval($id)
4624         );
4625
4626         if(! count($r)) {
4627                 if(! $interactive)
4628                         return 0;
4629                 notice( t('Item not found.') . EOL);
4630                 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4631         }
4632
4633         $item = $r[0];
4634
4635         $owner = $item['uid'];
4636
4637         $cid = 0;
4638
4639         // check if logged in user is either the author or owner of this item
4640
4641         if(is_array($_SESSION['remote'])) {
4642                 foreach($_SESSION['remote'] as $visitor) {
4643                         if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4644                                 $cid = $visitor['cid'];
4645                                 break;
4646                         }
4647                 }
4648         }
4649
4650
4651         if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4652
4653                 // Check if we should do HTML-based delete confirmation
4654                 if($_REQUEST['confirm']) {
4655                         // <form> can't take arguments in its "action" parameter
4656                         // so add any arguments as hidden inputs
4657                         $query = explode_querystring($a->query_string);
4658                         $inputs = array();
4659                         foreach($query['args'] as $arg) {
4660                                 if(strpos($arg, 'confirm=') === false) {
4661                                         $arg_parts = explode('=', $arg);
4662                                         $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4663                                 }
4664                         }
4665
4666                         return replace_macros(get_markup_template('confirm.tpl'), array(
4667                                 '$method' => 'get',
4668                                 '$message' => t('Do you really want to delete this item?'),
4669                                 '$extra_inputs' => $inputs,
4670                                 '$confirm' => t('Yes'),
4671                                 '$confirm_url' => $query['base'],
4672                                 '$confirm_name' => 'confirmed',
4673                                 '$cancel' => t('Cancel'),
4674                         ));
4675                 }
4676                 // Now check how the user responded to the confirmation query
4677                 if($_REQUEST['canceled']) {
4678                         goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4679                 }
4680
4681                 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4682                 // delete the item
4683
4684                 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4685                         dbesc(datetime_convert()),
4686                         dbesc(datetime_convert()),
4687                         intval($item['id'])
4688                 );
4689                 create_tags_from_item($item['id']);
4690                 create_files_from_item($item['id']);
4691                 delete_thread($item['id'], $item['parent-uri']);
4692
4693                 // clean up categories and tags so they don't end up as orphans
4694
4695                 $matches = false;
4696                 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4697                 if($cnt) {
4698                         foreach($matches as $mtch) {
4699                                 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4700                         }
4701                 }
4702
4703                 $matches = false;
4704
4705                 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4706                 if($cnt) {
4707                         foreach($matches as $mtch) {
4708                                 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4709                         }
4710                 }
4711
4712                 // If item is a link to a photo resource, nuke all the associated photos
4713                 // (visitors will not have photo resources)
4714                 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4715                 // generate a resource-id and therefore aren't intimately linked to the item.
4716
4717                 if(strlen($item['resource-id'])) {
4718                         q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4719                                 dbesc($item['resource-id']),
4720                                 intval($item['uid'])
4721                         );
4722                         // ignore the result
4723                 }
4724
4725                 // If item is a link to an event, nuke the event record.
4726
4727                 if(intval($item['event-id'])) {
4728                         q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4729                                 intval($item['event-id']),
4730                                 intval($item['uid'])
4731                         );
4732                         // ignore the result
4733                 }
4734
4735                 // clean up item_id and sign meta-data tables
4736
4737                 /*
4738                 // Old code - caused very long queries and warning entries in the mysql logfiles:
4739
4740                 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4741                         intval($item['id']),
4742                         intval($item['uid'])
4743                 );
4744
4745                 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4746                         intval($item['id']),
4747                         intval($item['uid'])
4748                 );
4749                 */
4750
4751                 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4752
4753                 // Creating list of parents
4754                 $r = q("select id from item where parent = %d and uid = %d",
4755                         intval($item['id']),
4756                         intval($item['uid'])
4757                 );
4758
4759                 $parentid = "";
4760
4761                 foreach ($r AS $row) {
4762                         if ($parentid != "")
4763                                 $parentid .= ", ";
4764
4765                         $parentid .= $row["id"];
4766                 }
4767
4768                 // Now delete them
4769                 if ($parentid != "") {
4770                         $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4771
4772                         $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4773                 }
4774
4775                 // If it's the parent of a comment thread, kill all the kids
4776
4777                 if($item['uri'] == $item['parent-uri']) {
4778                         $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4779                                 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4780                                 dbesc(datetime_convert()),
4781                                 dbesc(datetime_convert()),
4782                                 dbesc($item['parent-uri']),
4783                                 intval($item['uid'])
4784                         );
4785                         create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4786                         create_files_from_itemuri($item['parent-uri'], $item['uid']);
4787                         delete_thread_uri($item['parent-uri'], $item['uid']);
4788                         // ignore the result
4789                 }
4790                 else {
4791                         // ensure that last-child is set in case the comment that had it just got wiped.
4792                         q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4793                                 dbesc(datetime_convert()),
4794                                 dbesc($item['parent-uri']),
4795                                 intval($item['uid'])
4796                         );
4797                         // who is the last child now?
4798                         $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",
4799                                 dbesc($item['parent-uri']),
4800                                 intval($item['uid'])
4801                         );
4802                         if(count($r)) {
4803                                 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4804                                         intval($r[0]['id'])
4805                                 );
4806                         }
4807
4808                         // Add a relayable_retraction signature for Diaspora.
4809                         store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4810                 }
4811                 $drop_id = intval($item['id']);
4812
4813                 // send the notification upstream/downstream as the case may be
4814
4815                 proc_run('php',"include/notifier.php","drop","$drop_id");
4816
4817                 if(! $interactive)
4818                         return $owner;
4819                 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4820                 //NOTREACHED
4821         }
4822         else {
4823                 if(! $interactive)
4824                         return 0;
4825                 notice( t('Permission denied.') . EOL);
4826                 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4827                 //NOTREACHED
4828         }
4829
4830 }
4831
4832
4833 function first_post_date($uid,$wall = false) {
4834         $r = q("select id, created from item
4835                 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4836                 and id = parent
4837                 order by created asc limit 1",
4838                 intval($uid),
4839                 intval($wall ? 1 : 0)
4840         );
4841         if(count($r)) {
4842 //              logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4843                 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4844         }
4845         return false;
4846 }
4847
4848 function posted_dates($uid,$wall) {
4849         $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4850
4851         $dthen = first_post_date($uid,$wall);
4852         if(! $dthen)
4853                 return array();
4854
4855         // Set the start and end date to the beginning of the month
4856         $dnow = substr($dnow,0,8).'01';
4857         $dthen = substr($dthen,0,8).'01';
4858
4859         $ret = array();
4860         // Starting with the current month, get the first and last days of every
4861         // month down to and including the month of the first post
4862         while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4863                 $dstart = substr($dnow,0,8) . '01';
4864                 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4865                 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4866                 $end_month = datetime_convert('','',$dend,'Y-m-d');
4867                 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4868                 $ret[] = array($str,$end_month,$start_month);
4869                 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4870         }
4871         return $ret;
4872 }
4873
4874
4875 function posted_date_widget($url,$uid,$wall) {
4876         $o = '';
4877
4878         if(! feature_enabled($uid,'archives'))
4879                 return $o;
4880
4881         // For former Facebook folks that left because of "timeline"
4882
4883 /*      if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4884                 return $o;*/
4885
4886         $ret = posted_dates($uid,$wall);
4887         if(! count($ret))
4888                 return $o;
4889
4890         $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4891                 '$title' => t('Archives'),
4892                 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4893                 '$url' => $url,
4894                 '$dates' => $ret
4895         ));
4896         return $o;
4897 }
4898
4899 function store_diaspora_retract_sig($item, $user, $baseurl) {
4900         // Note that we can't add a target_author_signature
4901         // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4902         // the comment, that means we're the home of the post, and Diaspora will only
4903         // check the parent_author_signature of retractions that it doesn't have to relay further
4904         //
4905         // I don't think this function gets called for an "unlike," but I'll check anyway
4906
4907         $enabled = intval(get_config('system','diaspora_enabled'));
4908         if(! $enabled) {
4909                 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4910                 return;
4911         }
4912
4913         logger('drop_item: storing diaspora retraction signature');
4914
4915         $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4916
4917         if(local_user() == $item['uid']) {
4918
4919                 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4920                 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4921         }
4922         else {
4923                 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4924                         $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4925                 );
4926                 if(count($r)) {
4927                         // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4928                         // only handles DFRN deletes
4929                         $handle_baseurl_start = strpos($r['url'],'://') + 3;
4930                         $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4931                         $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4932                         $authorsig = '';
4933                 }
4934         }
4935
4936         if(isset($handle))
4937                 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4938                         intval($item['id']),
4939                         dbesc($signed_text),
4940                         dbesc($authorsig),
4941                         dbesc($handle)
4942                 );
4943
4944         return;
4945 }