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