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