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