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