]> git.mxchange.org Git - friendica.git/blob - include/items.php
c9e13b1a1f3f084cfff8803032b848254103c743
[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 `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1",
2772                                                 intval($datarray['uid']),
2773                                                 dbesc($datarray['author-link']),
2774                                                 dbesc($datarray['verb']),
2775                                                 dbesc($datarray['parent-uri'])
2776                                         );
2777                                         if($r && count($r))
2778                                                 continue;
2779
2780                                         $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1",
2781                                                 intval($datarray['uid']),
2782                                                 dbesc($datarray['author-link']),
2783                                                 dbesc($datarray['verb']),
2784                                                 dbesc($datarray['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 `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1",
3732                                                 intval($datarray['uid']),
3733                                                 dbesc($datarray['author-link']),
3734                                                 dbesc($datarray['verb']),
3735                                                 dbesc($datarray['parent-uri'])
3736                                         );
3737                                         if($r && count($r))
3738                                                 continue;
3739
3740                                         $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1",
3741                                                 intval($datarray['uid']),
3742                                                 dbesc($datarray['author-link']),
3743                                                 dbesc($datarray['verb']),
3744                                                 dbesc($datarray['parent-uri'])
3745
3746                                         );
3747                                         if($r && count($r))
3748                                                 continue;
3749                                 }
3750
3751                                 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3752
3753                                         $xo = parse_xml_string($datarray['object'],false);
3754                                         $xt = parse_xml_string($datarray['target'],false);
3755
3756                                         if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3757
3758                                                 // fetch the parent item
3759
3760                                                 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3761                                                         dbesc($xt->id),
3762                                                         intval($importer['importer_uid'])
3763                                                 );
3764                                                 if(! count($tagp))
3765                                                         continue;
3766
3767                                                 // extract tag, if not duplicate, and this user allows tags, add to parent item
3768
3769                                                 if($xo->id && $xo->content) {
3770                                                         $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3771                                                         if(! (stristr($tagp[0]['tag'],$newtag))) {
3772                                                                 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3773                                                                         intval($importer['importer_uid'])
3774                                                                 );
3775                                                                 if(count($i) && ! intval($i[0]['blocktags'])) {
3776                                                                         q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3777                                                                                 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3778                                                                                 intval($tagp[0]['id']),
3779                                                                                 dbesc(datetime_convert()),
3780                                                                                 dbesc(datetime_convert())
3781                                                                         );
3782                                                                         create_tags_from_item($tagp[0]['id']);
3783                                                                 }
3784                                                         }
3785                                                 }
3786                                         }
3787                                 }
3788
3789
3790                                 $posted_id = item_store($datarray);
3791                                 $parent = 0;
3792
3793                                 if($posted_id) {
3794
3795                                         $datarray["id"] = $posted_id;
3796
3797                                         $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3798                                                 intval($posted_id),
3799                                                 intval($importer['importer_uid'])
3800                                         );
3801                                         if(count($r)) {
3802                                                 $parent = $r[0]['parent'];
3803                                                 $parent_uri = $r[0]['parent-uri'];
3804                                         }
3805
3806                                         if(! $is_like) {
3807                                                 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3808                                                         dbesc(datetime_convert()),
3809                                                         intval($importer['importer_uid']),
3810                                                         intval($r[0]['parent'])
3811                                                 );
3812
3813                                                 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3814                                                         dbesc(datetime_convert()),
3815                                                         intval($importer['importer_uid']),
3816                                                         intval($posted_id)
3817                                                 );
3818                                         }
3819
3820                                         if($posted_id && $parent) {
3821
3822                                                 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3823
3824                                                 if((! $is_like) && (! $importer['self'])) {
3825
3826                                                         require_once('include/enotify.php');
3827
3828                                                         notification(array(
3829                                                                 'type'         => NOTIFY_COMMENT,
3830                                                                 'notify_flags' => $importer['notify-flags'],
3831                                                                 'language'     => $importer['language'],
3832                                                                 'to_name'      => $importer['username'],
3833                                                                 'to_email'     => $importer['email'],
3834                                                                 'uid'          => $importer['importer_uid'],
3835                                                                 'item'         => $datarray,
3836                                                                 'link'             => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3837                                                                 'source_name'  => stripslashes($datarray['author-name']),
3838                                                                 'source_link'  => $datarray['author-link'],
3839                                                                 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3840                                                                         ? $importer['thumb'] : $datarray['author-avatar']),
3841                                                                 'verb'         => ACTIVITY_POST,
3842                                                                 'otype'        => 'item',
3843                                                                 'parent'       => $parent,
3844                                                                 'parent_uri'   => $parent_uri,
3845                                                         ));
3846
3847                                                 }
3848                                         }
3849
3850                                         return 0;
3851                                         // NOTREACHED
3852                                 }
3853                         }
3854                         else {
3855
3856                                 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3857
3858                                 $item_id  = $item->get_id();
3859                                 $datarray = get_atom_elements($feed,$item);
3860
3861                                 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3862                                         continue;
3863
3864                                 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3865                                         dbesc($item_id),
3866                                         intval($importer['importer_uid'])
3867                                 );
3868
3869                                 // Update content if 'updated' changes
3870
3871                                 if(count($r)) {
3872                                         if (edited_timestamp_is_newer($r[0], $datarray)) {
3873
3874                                                 // do not accept (ignore) an earlier edit than one we currently have.
3875                                                 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3876                                                         continue;
3877
3878                                                 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3879                                                         dbesc($datarray['title']),
3880                                                         dbesc($datarray['body']),
3881                                                         dbesc($datarray['tag']),
3882                                                         dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3883                                                         dbesc(datetime_convert()),
3884                                                         dbesc($item_id),
3885                                                         intval($importer['importer_uid'])
3886                                                 );
3887                                                 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3888                                         }
3889
3890                                         // update last-child if it changes
3891
3892                                         $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3893                                         if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3894                                                 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3895                                                         dbesc(datetime_convert()),
3896                                                         dbesc($parent_uri),
3897                                                         intval($importer['importer_uid'])
3898                                                 );
3899                                                 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s'  WHERE `uri` = '%s' AND `uid` = %d",
3900                                                         intval($allow[0]['data']),
3901                                                         dbesc(datetime_convert()),
3902                                                         dbesc($item_id),
3903                                                         intval($importer['importer_uid'])
3904                                                 );
3905                                         }
3906                                         continue;
3907                                 }
3908
3909                                 $datarray['parent-uri'] = $parent_uri;
3910                                 $datarray['uid'] = $importer['importer_uid'];
3911                                 $datarray['contact-id'] = $importer['id'];
3912                                 if(($datarray['verb'] === ACTIVITY_LIKE)
3913                                         || ($datarray['verb'] === ACTIVITY_DISLIKE)
3914                                         || ($datarray['verb'] === ACTIVITY_ATTEND)
3915                                         || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3916                                         || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3917                                         $datarray['type'] = 'activity';
3918                                         $datarray['gravity'] = GRAVITY_LIKE;
3919                                         // only one like or dislike per person
3920                                         // splitted into two queries for performance issues
3921                                         $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1",
3922                                                 intval($datarray['uid']),
3923                                                 dbesc($datarray['author-link']),
3924                                                 dbesc($datarray['verb']),
3925                                                 dbesc($datarray['parent-uri'])
3926                                         );
3927                                         if($r && count($r))
3928                                                 continue;
3929
3930                                         $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1",
3931                                                 intval($datarray['uid']),
3932                                                 dbesc($datarray['author-link']),
3933                                                 dbesc($datarray['verb']),
3934                                                 dbesc($datarray['parent-uri'])
3935                                         );
3936                                         if($r && count($r))
3937                                                 continue;
3938
3939                                 }
3940
3941                                 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3942
3943                                         $xo = parse_xml_string($datarray['object'],false);
3944                                         $xt = parse_xml_string($datarray['target'],false);
3945
3946                                         if($xt->type == ACTIVITY_OBJ_NOTE) {
3947                                                 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3948                                                         dbesc($xt->id),
3949                                                         intval($importer['importer_uid'])
3950                                                 );
3951                                                 if(! count($r))
3952                                                         continue;
3953
3954                                                 // extract tag, if not duplicate, add to parent item
3955                                                 if($xo->content) {
3956                                                         if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3957                                                                 q("UPDATE item SET tag = '%s' WHERE id = %d",
3958                                                                         dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3959                                                                         intval($r[0]['id'])
3960                                                                 );
3961                                                                 create_tags_from_item($r[0]['id']);
3962                                                         }
3963                                                 }
3964                                         }
3965                                 }
3966
3967                                 $posted_id = item_store($datarray);
3968
3969                                 // find out if our user is involved in this conversation and wants to be notified.
3970
3971                                 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3972
3973                                         $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3974                                                 dbesc($top_uri),
3975                                                 intval($importer['importer_uid'])
3976                                         );
3977
3978                                         if(count($myconv)) {
3979                                                 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3980
3981                                                 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3982                                                 if(! link_compare($datarray['author-link'],$importer_url)) {
3983
3984
3985                                                         foreach($myconv as $conv) {
3986
3987                                                                 // now if we find a match, it means we're in this conversation
3988
3989                                                                 if(! link_compare($conv['author-link'],$importer_url))
3990                                                                         continue;
3991
3992                                                                 require_once('include/enotify.php');
3993
3994                                                                 $conv_parent = $conv['parent'];
3995
3996                                                                 notification(array(
3997                                                                         'type'         => NOTIFY_COMMENT,
3998                                                                         'notify_flags' => $importer['notify-flags'],
3999                                                                         'language'     => $importer['language'],
4000                                                                         'to_name'      => $importer['username'],
4001                                                                         'to_email'     => $importer['email'],
4002                                                                         'uid'          => $importer['importer_uid'],
4003                                                                         'item'         => $datarray,
4004                                                                         'link'             => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4005                                                                         'source_name'  => stripslashes($datarray['author-name']),
4006                                                                         'source_link'  => $datarray['author-link'],
4007                                                                         'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4008                                                                                 ? $importer['thumb'] : $datarray['author-avatar']),
4009                                                                         'verb'         => ACTIVITY_POST,
4010                                                                         'otype'        => 'item',
4011                                                                         'parent'       => $conv_parent,
4012                                                                         'parent_uri'   => $parent_uri
4013
4014                                                                 ));
4015
4016                                                                 // only send one notification
4017                                                                 break;
4018                                                         }
4019                                                 }
4020                                         }
4021                                 }
4022                                 continue;
4023                         }
4024                 }
4025
4026                 else {
4027
4028                         // Head post of a conversation. Have we seen it? If not, import it.
4029
4030
4031                         $item_id  = $item->get_id();
4032                         $datarray = get_atom_elements($feed,$item);
4033
4034                         if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
4035                                 $ev = bbtoevent($datarray['body']);
4036                                 if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
4037                                         $ev['cid'] = $importer['id'];
4038                                         $ev['uid'] = $importer['uid'];
4039                                         $ev['uri'] = $item_id;
4040                                         $ev['edited'] = $datarray['edited'];
4041                                         $ev['private'] = $datarray['private'];
4042                                         $ev['guid'] = $datarray['guid'];
4043
4044                                         $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4045                                                 dbesc($item_id),
4046                                                 intval($importer['uid'])
4047                                         );
4048                                         if(count($r))
4049                                                 $ev['id'] = $r[0]['id'];
4050                                         $xyz = event_store($ev);
4051                                         continue;
4052                                 }
4053                         }
4054
4055                         $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4056                                 dbesc($item_id),
4057                                 intval($importer['importer_uid'])
4058                         );
4059
4060                         // Update content if 'updated' changes
4061
4062                         if(count($r)) {
4063                                 if (edited_timestamp_is_newer($r[0], $datarray)) {
4064
4065                                         // do not accept (ignore) an earlier edit than one we currently have.
4066                                         if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
4067                                                 continue;
4068
4069                                         $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4070                                                 dbesc($datarray['title']),
4071                                                 dbesc($datarray['body']),
4072                                                 dbesc($datarray['tag']),
4073                                                 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
4074                                                 dbesc(datetime_convert()),
4075                                                 dbesc($item_id),
4076                                                 intval($importer['importer_uid'])
4077                                         );
4078                                         create_tags_from_itemuri($item_id, $importer['importer_uid']);
4079                                         update_thread_uri($item_id, $importer['importer_uid']);
4080                                 }
4081
4082                                 // update last-child if it changes
4083
4084                                 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
4085                                 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
4086                                         $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4087                                                 intval($allow[0]['data']),
4088                                                 dbesc(datetime_convert()),
4089                                                 dbesc($item_id),
4090                                                 intval($importer['importer_uid'])
4091                                         );
4092                                 }
4093                                 continue;
4094                         }
4095
4096                         $datarray['parent-uri'] = $item_id;
4097                         $datarray['uid'] = $importer['importer_uid'];
4098                         $datarray['contact-id'] = $importer['id'];
4099
4100
4101                         if(! link_compare($datarray['owner-link'],$importer['url'])) {
4102                                 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
4103                                 // but otherwise there's a possible data mixup on the sender's system.
4104                                 // the tgroup delivery code called from item_store will correct it if it's a forum,
4105                                 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
4106                                 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
4107                                 $datarray['owner-name']   = $importer['senderName'];
4108                                 $datarray['owner-link']   = $importer['url'];
4109                                 $datarray['owner-avatar'] = $importer['thumb'];
4110                         }
4111
4112                         if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
4113                                 continue;
4114
4115                         // This is my contact on another system, but it's really me.
4116                         // Turn this into a wall post.
4117                         $notify = item_is_remote_self($importer, $datarray);
4118
4119                         $posted_id = item_store($datarray, false, $notify);
4120
4121                         if(stristr($datarray['verb'],ACTIVITY_POKE)) {
4122                                 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
4123                                 if(! $verb)
4124                                         continue;
4125                                 $xo = parse_xml_string($datarray['object'],false);
4126
4127                                 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
4128
4129                                         // somebody was poked/prodded. Was it me?
4130
4131                                         $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
4132
4133                                 foreach($links->link as $l) {
4134                                 $atts = $l->attributes();
4135                                 switch($atts['rel']) {
4136                                         case "alternate":
4137                                                                 $Blink = $atts['href'];
4138                                                                 break;
4139                                                         default:
4140                                                                 break;
4141                                     }
4142                                 }
4143                                         if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
4144
4145                                                 // send a notification
4146                                                 require_once('include/enotify.php');
4147
4148                                                 notification(array(
4149                                                         'type'         => NOTIFY_POKE,
4150                                                         'notify_flags' => $importer['notify-flags'],
4151                                                         'language'     => $importer['language'],
4152                                                         'to_name'      => $importer['username'],
4153                                                         'to_email'     => $importer['email'],
4154                                                         'uid'          => $importer['importer_uid'],
4155                                                         'item'         => $datarray,
4156                                                         'link'             => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4157                                                         'source_name'  => stripslashes($datarray['author-name']),
4158                                                         'source_link'  => $datarray['author-link'],
4159                                                         'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4160                                                                 ? $importer['thumb'] : $datarray['author-avatar']),
4161                                                         'verb'         => $datarray['verb'],
4162                                                         'otype'        => 'person',
4163                                                         'activity'     => $verb,
4164                                                         'parent'       => $datarray['parent']
4165                                                 ));
4166                                         }
4167                                 }
4168                         }
4169
4170                         continue;
4171                 }
4172         }
4173
4174         return 0;
4175         // NOTREACHED
4176
4177 }
4178
4179
4180 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4181         $url = notags(trim($datarray['author-link']));
4182         $name = notags(trim($datarray['author-name']));
4183         $photo = notags(trim($datarray['author-avatar']));
4184
4185         if (is_object($item)) {
4186                 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4187                 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4188                         $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4189         } else
4190                 $nick = $item;
4191
4192         if(is_array($contact)) {
4193                 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4194                         || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4195                         $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4196                                 intval(CONTACT_IS_FRIEND),
4197                                 intval($contact['id']),
4198                                 intval($importer['uid'])
4199                         );
4200                 }
4201                 // send email notification to owner?
4202         } else {
4203
4204                 // create contact record
4205
4206                 $r = q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4207                         `blocked`, `readonly`, `pending`, `writable`)
4208                         VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
4209                         intval($importer['uid']),
4210                         dbesc(datetime_convert()),
4211                         dbesc($url),
4212                         dbesc(normalise_link($url)),
4213                         dbesc($name),
4214                         dbesc($nick),
4215                         dbesc($photo),
4216                         dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4217                         intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4218                 );
4219                 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4220                                 intval($importer['uid']),
4221                                 dbesc($url)
4222                 );
4223                 if(count($r)) {
4224                                 $contact_record = $r[0];
4225
4226                                 $photos = import_profile_photo($photo,$importer["uid"],$contact_record["id"]);
4227
4228                                 q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s' WHERE `id` = %d",
4229                                         dbesc($photos[0]),
4230                                         dbesc($photos[1]),
4231                                         dbesc($photos[2]),
4232                                         intval($contact_record["id"])
4233                                 );
4234                 }
4235
4236
4237                 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4238                         intval($importer['uid'])
4239                 );
4240                 $a = get_app();
4241                 if(count($r) AND !in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
4242
4243                         // create notification
4244                         $hash = random_string();
4245
4246                         if(is_array($contact_record)) {
4247                                 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4248                                         VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4249                                         intval($importer['uid']),
4250                                         intval($contact_record['id']),
4251                                         dbesc($hash),
4252                                         dbesc(datetime_convert())
4253                                 );
4254                         }
4255
4256                         if(intval($r[0]['def_gid'])) {
4257                                 require_once('include/group.php');
4258                                 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4259                         }
4260
4261                         if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4262                                 in_array($r[0]['page-flags'], array(PAGE_NORMAL))) {
4263
4264                                 notification(array(
4265                                         'type'         => NOTIFY_INTRO,
4266                                         'notify_flags' => $r[0]['notify-flags'],
4267                                         'language'     => $r[0]['language'],
4268                                         'to_name'      => $r[0]['username'],
4269                                         'to_email'     => $r[0]['email'],
4270                                         'uid'          => $r[0]['uid'],
4271                                         'link'             => $a->get_baseurl() . '/notifications/intro',
4272                                         'source_name'  => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4273                                         'source_link'  => $contact_record['url'],
4274                                         'source_photo' => $contact_record['photo'],
4275                                         'verb'         => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4276                                         'otype'        => 'intro'
4277                                 ));
4278
4279                         }
4280                 } elseif (count($r) AND in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
4281                         $r = q("UPDATE `contact` SET `pending` = 0 WHERE `uid` = %d AND `url` = '%s' AND `pending` LIMIT 1",
4282                                         intval($importer['uid']),
4283                                         dbesc($url)
4284                         );
4285                 }
4286
4287         }
4288 }
4289
4290 function lose_follower($importer,$contact,$datarray,$item) {
4291
4292         if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4293                 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4294                         intval(CONTACT_IS_SHARING),
4295                         intval($contact['id'])
4296                 );
4297         }
4298         else {
4299                 contact_remove($contact['id']);
4300         }
4301 }
4302
4303 function lose_sharer($importer,$contact,$datarray,$item) {
4304
4305         if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4306                 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4307                         intval(CONTACT_IS_FOLLOWER),
4308                         intval($contact['id'])
4309                 );
4310         }
4311         else {
4312                 contact_remove($contact['id']);
4313         }
4314 }
4315
4316
4317 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4318
4319         $a = get_app();
4320
4321         if(is_array($importer)) {
4322                 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4323                         intval($importer['uid'])
4324                 );
4325         }
4326
4327         // Diaspora has different message-ids in feeds than they do
4328         // through the direct Diaspora protocol. If we try and use
4329         // the feed, we'll get duplicates. So don't.
4330
4331         if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4332                 return;
4333
4334         $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4335
4336         // Use a single verify token, even if multiple hubs
4337
4338         $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4339
4340         $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4341
4342         logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: '  . $push_url . ' with verifier ' . $verify_token);
4343
4344         if(!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
4345                 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4346                         dbesc($verify_token),
4347                         intval($contact['id'])
4348                 );
4349         }
4350
4351         post_url($url,$params);
4352
4353         logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4354
4355         return;
4356
4357 }
4358
4359
4360 function atom_author($tag,$name,$uri,$h,$w,$photo,$geo) {
4361         $o = '';
4362         if(! $tag)
4363                 return $o;
4364         $name = xmlify($name);
4365         $uri = xmlify($uri);
4366         $h = intval($h);
4367         $w = intval($w);
4368         $photo = xmlify($photo);
4369
4370
4371         $o .= "<$tag>\r\n";
4372         $o .= "\t<name>$name</name>\r\n";
4373         $o .= "\t<uri>$uri</uri>\r\n";
4374         $o .= "\t".'<link rel="photo"  type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4375         $o .= "\t".'<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4376
4377         if ($tag == "author") {
4378
4379                 if($geo)
4380                         $o .= '<georss:point>'.xmlify($geo).'</georss:point>'."\r\n";
4381
4382                 $r = q("SELECT `profile`.`locality`, `profile`.`region`, `profile`.`country-name`,
4383                                 `profile`.`name`, `profile`.`pub_keywords`, `profile`.`about`,
4384                                 `profile`.`homepage`,`contact`.`nick` FROM `profile`
4385                                 INNER JOIN `contact` ON `contact`.`uid` = `profile`.`uid`
4386                                 INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
4387                                 WHERE `profile`.`is-default` AND `contact`.`self` AND
4388                                         NOT `user`.`hidewall` AND `contact`.`nurl`='%s'",
4389                         dbesc(normalise_link($uri)));
4390                 if ($r) {
4391                         $location = '';
4392                         if($r[0]['locality'])
4393                                 $location .= $r[0]['locality'];
4394                         if($r[0]['region']) {
4395                                 if($location)
4396                                         $location .= ', ';
4397                                 $location .= $r[0]['region'];
4398                         }
4399                         if($r[0]['country-name']) {
4400                                 if($location)
4401                                         $location .= ', ';
4402                                 $location .= $r[0]['country-name'];
4403                         }
4404
4405                         $o .= "\t<poco:preferredUsername>".xmlify($r[0]["nick"])."</poco:preferredUsername>\r\n";
4406                         $o .= "\t<poco:displayName>".xmlify($r[0]["name"])."</poco:displayName>\r\n";
4407                         $o .= "\t<poco:note>".xmlify(bbcode($r[0]["about"]))."</poco:note>\r\n";
4408                         $o .= "\t<poco:address>\r\n";
4409                         $o .= "\t\t<poco:formatted>".xmlify($location)."</poco:formatted>\r\n";
4410                         $o .= "\t</poco:address>\r\n";
4411                         $o .= "\t<poco:urls>\r\n";
4412                         $o .= "\t<poco:type>homepage</poco:type>\r\n";
4413                         $o .= "\t\t<poco:value>".xmlify($r[0]["homepage"])."</poco:value>\r\n";
4414                         $o .= "\t\t<poco:primary>true</poco:primary>\r\n";
4415                         $o .= "\t</poco:urls>\r\n";
4416                 }
4417         }
4418
4419         call_hooks('atom_author', $o);
4420
4421         $o .= "</$tag>\r\n";
4422         return $o;
4423 }
4424
4425 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4426
4427         $a = get_app();
4428
4429         if(! $item['parent'])
4430                 return;
4431
4432         if($item['deleted'])
4433                 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4434
4435
4436         if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4437                 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4438         else
4439                 $body = $item['body'];
4440
4441
4442         $o = "\r\n\r\n<entry>\r\n";
4443
4444         if(is_array($author))
4445                 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb'], $item['coord']);
4446         else
4447                 $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']);
4448         if(strlen($item['owner-name']))
4449                 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar'], $item['coord']);
4450
4451         if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4452                 $parent = q("SELECT `guid` FROM `item` WHERE `id` = %d", intval($item["parent"]));
4453                 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4454                 $o .= '<thr:in-reply-to ref="'.xmlify($parent_item).'" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$parent[0]['guid']).'" />'."\r\n";
4455         }
4456
4457         $htmlbody = $body;
4458
4459         if ($item['title'] != "")
4460                 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4461
4462         $htmlbody = bbcode($htmlbody, false, false, 7);
4463
4464         $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4465         $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4466         $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4467         $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4468         $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4469         $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4470         $o .= '<link rel="alternate" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$item['guid']).'" />'."\r\n";
4471
4472         $o .= '<status_net notice_id="'.$item['id'].'"></status_net>'."\r\n";
4473
4474         if($comment)
4475                 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4476
4477         if($item['location']) {
4478                 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4479                 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4480         }
4481
4482         if($item['coord'])
4483                 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4484
4485         if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4486                 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4487
4488         if($item['extid'])
4489                 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4490         if($item['bookmark'])
4491                 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4492
4493         if($item['app'])
4494                 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4495
4496         if($item['guid'])
4497                 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4498
4499         if($item['signed_text']) {
4500                 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4501                 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4502         }
4503
4504         $verb = construct_verb($item);
4505         $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4506         $actobj = construct_activity_object($item);
4507         if(strlen($actobj))
4508                 $o .= $actobj;
4509         $actarg = construct_activity_target($item);
4510         if(strlen($actarg))
4511                 $o .= $actarg;
4512
4513         $tags = item_getfeedtags($item);
4514         if(count($tags)) {
4515                 foreach($tags as $t)
4516                         if (($type != 'html') OR ($t[0] != "@"))
4517                                 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4518         }
4519
4520         /// @TODO
4521         /// To support these elements, the API needs to be enhanced
4522         /// $o .= '<link rel="ostatus:conversation" href="'.xmlify($a->get_baseurl().'/display/'.$owner['nickname'].'/'.$item['parent']).'"/>'."\r\n";
4523         /// $o .= "\t".'<link rel="self" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4524         /// $o .= "\t".'<link rel="edit" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4525
4526         // Deactivated since it was meant only for OStatus
4527         //$o .= item_get_attachment($item);
4528
4529         $o .= item_getfeedattach($item);
4530
4531         $mentioned = get_mentions($item);
4532         if($mentioned)
4533                 $o .= $mentioned;
4534
4535         call_hooks('atom_entry', $o);
4536
4537         $o .= '</entry>' . "\r\n";
4538
4539         return $o;
4540 }
4541
4542 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4543
4544         if(get_config('system','disable_embedded'))
4545                 return $s;
4546
4547         $a = get_app();
4548
4549         logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4550         $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4551
4552         $orig_body = $s;
4553         $new_body = '';
4554
4555         $img_start = strpos($orig_body, '[img');
4556         $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4557         $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4558         while( ($img_st_close !== false) && ($img_len !== false) ) {
4559
4560                 $img_st_close++; // make it point to AFTER the closing bracket
4561                 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4562
4563                 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4564
4565
4566                 if(stristr($image , $site . '/photo/')) {
4567                         // Only embed locally hosted photos
4568                         $replace = false;
4569                         $i = basename($image);
4570                         $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4571                         $x = strpos($i,'-');
4572
4573                         if($x) {
4574                                 $res = substr($i,$x+1);
4575                                 $i = substr($i,0,$x);
4576                                 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4577                                         dbesc($i),
4578                                         intval($res),
4579                                         intval($uid)
4580                                 );
4581                                 if($r) {
4582
4583                                         // Check to see if we should replace this photo link with an embedded image
4584                                         // 1. No need to do so if the photo is public
4585                                         // 2. If there's a contact-id provided, see if they're in the access list
4586                                         //    for the photo. If so, embed it.
4587                                         // 3. Otherwise, if we have an item, see if the item permissions match the photo
4588                                         //    permissions, regardless of order but first check to see if they're an exact
4589                                         //    match to save some processing overhead.
4590
4591                                         if(has_permissions($r[0])) {
4592                                                 if($cid) {
4593                                                         $recips = enumerate_permissions($r[0]);
4594                                                         if(in_array($cid, $recips)) {
4595                                                                 $replace = true;
4596                                                         }
4597                                                 }
4598                                                 elseif($item) {
4599                                                         if(compare_permissions($item,$r[0]))
4600                                                                 $replace = true;
4601                                                 }
4602                                         }
4603                                         if($replace) {
4604                                                 $data = $r[0]['data'];
4605                                                 $type = $r[0]['type'];
4606
4607                                                 // If a custom width and height were specified, apply before embedding
4608                                                 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4609                                                         logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4610
4611                                                         $width = intval($match[1]);
4612                                                         $height = intval($match[2]);
4613
4614                                                         $ph = new Photo($data, $type);
4615                                                         if($ph->is_valid()) {
4616                                                                 $ph->scaleImage(max($width, $height));
4617                                                                 $data = $ph->imageString();
4618                                                                 $type = $ph->getType();
4619                                                         }
4620                                                 }
4621
4622                                                 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4623                                                 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4624                                                 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4625                                         }
4626                                 }
4627                         }
4628                 }
4629
4630                 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4631                 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4632                 if($orig_body === false)
4633                         $orig_body = '';
4634
4635                 $img_start = strpos($orig_body, '[img');
4636                 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4637                 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4638         }
4639
4640         $new_body = $new_body . $orig_body;
4641
4642         return($new_body);
4643 }
4644
4645
4646 function has_permissions($obj) {
4647         if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4648                 return true;
4649         return false;
4650 }
4651
4652 function compare_permissions($obj1,$obj2) {
4653         // first part is easy. Check that these are exactly the same.
4654         if(($obj1['allow_cid'] == $obj2['allow_cid'])
4655                 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4656                 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4657                 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4658                 return true;
4659
4660         // This is harder. Parse all the permissions and compare the resulting set.
4661
4662         $recipients1 = enumerate_permissions($obj1);
4663         $recipients2 = enumerate_permissions($obj2);
4664         sort($recipients1);
4665         sort($recipients2);
4666         if($recipients1 == $recipients2)
4667                 return true;
4668         return false;
4669 }
4670
4671 // returns an array of contact-ids that are allowed to see this object
4672
4673 function enumerate_permissions($obj) {
4674         require_once('include/group.php');
4675         $allow_people = expand_acl($obj['allow_cid']);
4676         $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4677         $deny_people  = expand_acl($obj['deny_cid']);
4678         $deny_groups  = expand_groups(expand_acl($obj['deny_gid']));
4679         $recipients   = array_unique(array_merge($allow_people,$allow_groups));
4680         $deny         = array_unique(array_merge($deny_people,$deny_groups));
4681         $recipients   = array_diff($recipients,$deny);
4682         return $recipients;
4683 }
4684
4685 function item_getfeedtags($item) {
4686         $ret = array();
4687         $matches = false;
4688         $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4689         if($cnt) {
4690                 for($x = 0; $x < $cnt; $x ++) {
4691                         if($matches[1][$x])
4692                                 $ret[$matches[2][$x]] = array('#',$matches[1][$x], $matches[2][$x]);
4693                 }
4694         }
4695         $matches = false;
4696         $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4697         if($cnt) {
4698                 for($x = 0; $x < $cnt; $x ++) {
4699                         if($matches[1][$x])
4700                                 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4701                 }
4702         }
4703         return $ret;
4704 }
4705
4706 function item_get_attachment($item) {
4707         $o = "";
4708         $siteinfo = get_attached_data($item["body"]);
4709
4710         switch($siteinfo["type"]) {
4711                 case 'link':
4712                         $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4713                         break;
4714                 case 'photo':
4715                         $imgdata = get_photo_info($siteinfo["image"]);
4716                         $o = '<link rel="enclosure" href="'.xmlify($siteinfo["image"]).'" type="'.$imgdata["mime"].'" length="'.$imgdata["size"].'"/>'."\r\n";
4717                         break;
4718                 case 'video':
4719                         $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4720                         break;
4721                 default:
4722                         break;
4723         }
4724
4725         return $o;
4726 }
4727
4728 function item_getfeedattach($item) {
4729         $ret = '';
4730         $arr = explode('[/attach],',$item['attach']);
4731         if(count($arr)) {
4732                 foreach($arr as $r) {
4733                         $matches = false;
4734                         $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4735                         if($cnt) {
4736                                 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4737                                 if(intval($matches[2]))
4738                                         $ret .= 'length="' . intval($matches[2]) . '" ';
4739                                 if($matches[4] !== ' ')
4740                                         $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4741                                 $ret .= ' />' . "\r\n";
4742                         }
4743                 }
4744         }
4745         return $ret;
4746 }
4747
4748
4749
4750 function item_expire($uid, $days, $network = "", $force = false) {
4751
4752         if((! $uid) || ($days < 1))
4753                 return;
4754
4755         // $expire_network_only = save your own wall posts
4756         // and just expire conversations started by others
4757
4758         $expire_network_only = get_pconfig($uid,'expire','network_only');
4759         $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4760
4761         if ($network != "") {
4762                 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4763                 // There is an index "uid_network_received" but not "uid_network_created"
4764                 // This avoids the creation of another index just for one purpose.
4765                 // And it doesn't really matter wether to look at "received" or "created"
4766                 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4767         } else
4768                 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4769
4770         $r = q("SELECT * FROM `item`
4771                 WHERE `uid` = %d $range
4772                 AND `id` = `parent`
4773                 $sql_extra
4774                 AND `deleted` = 0",
4775                 intval($uid),
4776                 intval($days)
4777         );
4778
4779         if(! count($r))
4780                 return;
4781
4782         $expire_items = get_pconfig($uid, 'expire','items');
4783         $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4784
4785         // Forcing expiring of items - but not notes and marked items
4786         if ($force)
4787                 $expire_items = true;
4788
4789         $expire_notes = get_pconfig($uid, 'expire','notes');
4790         $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4791
4792         $expire_starred = get_pconfig($uid, 'expire','starred');
4793         $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4794
4795         $expire_photos = get_pconfig($uid, 'expire','photos');
4796         $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4797
4798         logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4799
4800         foreach($r as $item) {
4801
4802                 // don't expire filed items
4803
4804                 if(strpos($item['file'],'[') !== false)
4805                         continue;
4806
4807                 // Only expire posts, not photos and photo comments
4808
4809                 if($expire_photos==0 && strlen($item['resource-id']))
4810                         continue;
4811                 if($expire_starred==0 && intval($item['starred']))
4812                         continue;
4813                 if($expire_notes==0 && $item['type']=='note')
4814                         continue;
4815                 if($expire_items==0 && $item['type']!='note')
4816                         continue;
4817
4818                 drop_item($item['id'],false);
4819         }
4820
4821         proc_run('php',"include/notifier.php","expire","$uid");
4822
4823 }
4824
4825
4826 function drop_items($items) {
4827         $uid = 0;
4828
4829         if(! local_user() && ! remote_user())
4830                 return;
4831
4832         if(count($items)) {
4833                 foreach($items as $item) {
4834                         $owner = drop_item($item,false);
4835                         if($owner && ! $uid)
4836                                 $uid = $owner;
4837                 }
4838         }
4839
4840         // multiple threads may have been deleted, send an expire notification
4841
4842         if($uid)
4843                 proc_run('php',"include/notifier.php","expire","$uid");
4844 }
4845
4846
4847 function drop_item($id,$interactive = true) {
4848
4849         $a = get_app();
4850
4851         // locate item to be deleted
4852
4853         $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4854                 intval($id)
4855         );
4856
4857         if(! count($r)) {
4858                 if(! $interactive)
4859                         return 0;
4860                 notice( t('Item not found.') . EOL);
4861                 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4862         }
4863
4864         $item = $r[0];
4865
4866         $owner = $item['uid'];
4867
4868         $cid = 0;
4869
4870         // check if logged in user is either the author or owner of this item
4871
4872         if(is_array($_SESSION['remote'])) {
4873                 foreach($_SESSION['remote'] as $visitor) {
4874                         if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4875                                 $cid = $visitor['cid'];
4876                                 break;
4877                         }
4878                 }
4879         }
4880
4881
4882         if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4883
4884                 // Check if we should do HTML-based delete confirmation
4885                 if($_REQUEST['confirm']) {
4886                         // <form> can't take arguments in its "action" parameter
4887                         // so add any arguments as hidden inputs
4888                         $query = explode_querystring($a->query_string);
4889                         $inputs = array();
4890                         foreach($query['args'] as $arg) {
4891                                 if(strpos($arg, 'confirm=') === false) {
4892                                         $arg_parts = explode('=', $arg);
4893                                         $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4894                                 }
4895                         }
4896
4897                         return replace_macros(get_markup_template('confirm.tpl'), array(
4898                                 '$method' => 'get',
4899                                 '$message' => t('Do you really want to delete this item?'),
4900                                 '$extra_inputs' => $inputs,
4901                                 '$confirm' => t('Yes'),
4902                                 '$confirm_url' => $query['base'],
4903                                 '$confirm_name' => 'confirmed',
4904                                 '$cancel' => t('Cancel'),
4905                         ));
4906                 }
4907                 // Now check how the user responded to the confirmation query
4908                 if($_REQUEST['canceled']) {
4909                         goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4910                 }
4911
4912                 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4913                 // delete the item
4914
4915                 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4916                         dbesc(datetime_convert()),
4917                         dbesc(datetime_convert()),
4918                         intval($item['id'])
4919                 );
4920                 create_tags_from_item($item['id']);
4921                 create_files_from_item($item['id']);
4922                 delete_thread($item['id'], $item['parent-uri']);
4923
4924                 // clean up categories and tags so they don't end up as orphans
4925
4926                 $matches = false;
4927                 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4928                 if($cnt) {
4929                         foreach($matches as $mtch) {
4930                                 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4931                         }
4932                 }
4933
4934                 $matches = false;
4935
4936                 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4937                 if($cnt) {
4938                         foreach($matches as $mtch) {
4939                                 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4940                         }
4941                 }
4942
4943                 // If item is a link to a photo resource, nuke all the associated photos
4944                 // (visitors will not have photo resources)
4945                 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4946                 // generate a resource-id and therefore aren't intimately linked to the item.
4947
4948                 if(strlen($item['resource-id'])) {
4949                         q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4950                                 dbesc($item['resource-id']),
4951                                 intval($item['uid'])
4952                         );
4953                         // ignore the result
4954                 }
4955
4956                 // If item is a link to an event, nuke the event record.
4957
4958                 if(intval($item['event-id'])) {
4959                         q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4960                                 intval($item['event-id']),
4961                                 intval($item['uid'])
4962                         );
4963                         // ignore the result
4964                 }
4965
4966                 // If item has attachments, drop them
4967
4968                 foreach(explode(",",$item['attach']) as $attach){
4969                         preg_match("|attach/(\d+)|", $attach, $matches);
4970                         q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4971                                 intval($matches[1]),
4972                                 local_user()
4973                         );
4974                         // ignore the result
4975                 }
4976
4977
4978                 // clean up item_id and sign meta-data tables
4979
4980                 /*
4981                 // Old code - caused very long queries and warning entries in the mysql logfiles:
4982
4983                 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4984                         intval($item['id']),
4985                         intval($item['uid'])
4986                 );
4987
4988                 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4989                         intval($item['id']),
4990                         intval($item['uid'])
4991                 );
4992                 */
4993
4994                 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4995
4996                 // Creating list of parents
4997                 $r = q("select id from item where parent = %d and uid = %d",
4998                         intval($item['id']),
4999                         intval($item['uid'])
5000                 );
5001
5002                 $parentid = "";
5003
5004                 foreach ($r AS $row) {
5005                         if ($parentid != "")
5006                                 $parentid .= ", ";
5007
5008                         $parentid .= $row["id"];
5009                 }
5010
5011                 // Now delete them
5012                 if ($parentid != "") {
5013                         $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
5014
5015                         $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
5016                 }
5017
5018                 // If it's the parent of a comment thread, kill all the kids
5019
5020                 if($item['uri'] == $item['parent-uri']) {
5021                         $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
5022                                 WHERE `parent-uri` = '%s' AND `uid` = %d ",
5023                                 dbesc(datetime_convert()),
5024                                 dbesc(datetime_convert()),
5025                                 dbesc($item['parent-uri']),
5026                                 intval($item['uid'])
5027                         );
5028                         create_tags_from_itemuri($item['parent-uri'], $item['uid']);
5029                         create_files_from_itemuri($item['parent-uri'], $item['uid']);
5030                         delete_thread_uri($item['parent-uri'], $item['uid']);
5031                         // ignore the result
5032                 }
5033                 else {
5034                         // ensure that last-child is set in case the comment that had it just got wiped.
5035                         q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
5036                                 dbesc(datetime_convert()),
5037                                 dbesc($item['parent-uri']),
5038                                 intval($item['uid'])
5039                         );
5040                         // who is the last child now?
5041                         $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",
5042                                 dbesc($item['parent-uri']),
5043                                 intval($item['uid'])
5044                         );
5045                         if(count($r)) {
5046                                 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
5047                                         intval($r[0]['id'])
5048                                 );
5049                         }
5050
5051                         // Add a relayable_retraction signature for Diaspora.
5052                         store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
5053                 }
5054
5055                 $drop_id = intval($item['id']);
5056
5057                 // send the notification upstream/downstream as the case may be
5058
5059                 proc_run('php',"include/notifier.php","drop","$drop_id");
5060
5061                 if(! $interactive)
5062                         return $owner;
5063                 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5064                 //NOTREACHED
5065         }
5066         else {
5067                 if(! $interactive)
5068                         return 0;
5069                 notice( t('Permission denied.') . EOL);
5070                 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5071                 //NOTREACHED
5072         }
5073
5074 }
5075
5076
5077 function first_post_date($uid,$wall = false) {
5078         $r = q("select id, created from item
5079                 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
5080                 and id = parent
5081                 order by created asc limit 1",
5082                 intval($uid),
5083                 intval($wall ? 1 : 0)
5084         );
5085         if(count($r)) {
5086 //              logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
5087                 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
5088         }
5089         return false;
5090 }
5091
5092 /* modified posted_dates() {below} to arrange the list in years */
5093 function list_post_dates($uid, $wall) {
5094         $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5095
5096         $dthen = first_post_date($uid, $wall);
5097         if(! $dthen)
5098                 return array();
5099
5100         // Set the start and end date to the beginning of the month
5101         $dnow = substr($dnow,0,8).'01';
5102         $dthen = substr($dthen,0,8).'01';
5103
5104         $ret = array();
5105
5106         // Starting with the current month, get the first and last days of every
5107         // month down to and including the month of the first post
5108         while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5109                 $dyear = intval(substr($dnow,0,4));
5110                 $dstart = substr($dnow,0,8) . '01';
5111                 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5112                 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5113                 $end_month = datetime_convert('','',$dend,'Y-m-d');
5114                 $str = day_translate(datetime_convert('','',$dnow,'F'));
5115                 if(! $ret[$dyear])
5116                         $ret[$dyear] = array();
5117                 $ret[$dyear][] = array($str,$end_month,$start_month);
5118                 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5119         }
5120         return $ret;
5121 }
5122
5123 function posted_dates($uid,$wall) {
5124         $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5125
5126         $dthen = first_post_date($uid,$wall);
5127         if(! $dthen)
5128                 return array();
5129
5130         // Set the start and end date to the beginning of the month
5131         $dnow = substr($dnow,0,8).'01';
5132         $dthen = substr($dthen,0,8).'01';
5133
5134         $ret = array();
5135         // Starting with the current month, get the first and last days of every
5136         // month down to and including the month of the first post
5137         while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5138                 $dstart = substr($dnow,0,8) . '01';
5139                 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5140                 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5141                 $end_month = datetime_convert('','',$dend,'Y-m-d');
5142                 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
5143                 $ret[] = array($str,$end_month,$start_month);
5144                 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5145         }
5146         return $ret;
5147 }
5148
5149
5150 function posted_date_widget($url,$uid,$wall) {
5151         $o = '';
5152
5153         if(! feature_enabled($uid,'archives'))
5154                 return $o;
5155
5156         // For former Facebook folks that left because of "timeline"
5157
5158 /*      if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
5159                 return $o;*/
5160
5161         $visible_years = get_pconfig($uid,'system','archive_visible_years');
5162         if(! $visible_years)
5163                 $visible_years = 5;
5164
5165         $ret = list_post_dates($uid,$wall);
5166
5167         if(! count($ret))
5168                 return $o;
5169
5170         $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
5171         $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
5172
5173         $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
5174                 '$title' => t('Archives'),
5175                 '$size' => $visible_years,
5176                 '$cutoff_year' => $cutoff_year,
5177                 '$cutoff' => $cutoff,
5178                 '$url' => $url,
5179                 '$dates' => $ret,
5180                 '$showmore' => t('show more')
5181
5182         ));
5183         return $o;
5184 }
5185
5186 function store_diaspora_retract_sig($item, $user, $baseurl) {
5187         // Note that we can't add a target_author_signature
5188         // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
5189         // the comment, that means we're the home of the post, and Diaspora will only
5190         // check the parent_author_signature of retractions that it doesn't have to relay further
5191         //
5192         // I don't think this function gets called for an "unlike," but I'll check anyway
5193
5194         $enabled = intval(get_config('system','diaspora_enabled'));
5195         if(! $enabled) {
5196                 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
5197                 return;
5198         }
5199
5200         logger('drop_item: storing diaspora retraction signature');
5201
5202         $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
5203
5204         if(local_user() == $item['uid']) {
5205
5206                 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
5207                 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
5208         }
5209         else {
5210                 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
5211                         $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
5212                 );
5213                 if(count($r)) {
5214                         // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
5215                         // only handles DFRN deletes
5216                         $handle_baseurl_start = strpos($r['url'],'://') + 3;
5217                         $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
5218                         $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
5219                         $authorsig = '';
5220                 }
5221         }
5222
5223         if(isset($handle))
5224                 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
5225                         intval($item['id']),
5226                         dbesc($signed_text),
5227                         dbesc($authorsig),
5228                         dbesc($handle)
5229                 );
5230
5231         return;
5232 }