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