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