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