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