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