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