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