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