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