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