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