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