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