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