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