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