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