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