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