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