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