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