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