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