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