]> git.mxchange.org Git - friendica.git/blob - include/items.php
Merge pull request #1683 from fabrixxm/issue-1655
[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         $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4163         if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4164                 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4165
4166         if(is_array($contact)) {
4167                 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4168                         || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4169                         $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4170                                 intval(CONTACT_IS_FRIEND),
4171                                 intval($contact['id']),
4172                                 intval($importer['uid'])
4173                         );
4174                 }
4175                 // send email notification to owner?
4176         }
4177         else {
4178
4179                 // create contact record
4180
4181                 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4182                         `blocked`, `readonly`, `pending`, `writable` )
4183                         VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4184                         intval($importer['uid']),
4185                         dbesc(datetime_convert()),
4186                         dbesc($url),
4187                         dbesc(normalise_link($url)),
4188                         dbesc($name),
4189                         dbesc($nick),
4190                         dbesc($photo),
4191                         dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4192                         intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4193                 );
4194                 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4195                                 intval($importer['uid']),
4196                                 dbesc($url)
4197                 );
4198                 if(count($r))
4199                                 $contact_record = $r[0];
4200
4201                 // create notification
4202                 $hash = random_string();
4203
4204                 if(is_array($contact_record)) {
4205                         $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4206                                 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4207                                 intval($importer['uid']),
4208                                 intval($contact_record['id']),
4209                                 dbesc($hash),
4210                                 dbesc(datetime_convert())
4211                         );
4212                 }
4213
4214                 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4215                         intval($importer['uid'])
4216                 );
4217                 $a = get_app();
4218                 if(count($r)) {
4219
4220                         if(intval($r[0]['def_gid'])) {
4221                                 require_once('include/group.php');
4222                                 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4223                         }
4224
4225                         if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4226                                 in_array($r[0]['page-flags'], array(PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE))) {
4227
4228                                 notification(array(
4229                                         'type'         => NOTIFY_INTRO,
4230                                         'notify_flags' => $r[0]['notify-flags'],
4231                                         'language'     => $r[0]['language'],
4232                                         'to_name'      => $r[0]['username'],
4233                                         'to_email'     => $r[0]['email'],
4234                                         'uid'          => $r[0]['uid'],
4235                                         'link'             => $a->get_baseurl() . '/notifications/intro',
4236                                         'source_name'  => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4237                                         'source_link'  => $contact_record['url'],
4238                                         'source_photo' => $contact_record['photo'],
4239                                         'verb'         => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4240                                         'otype'        => 'intro'
4241                                 ));
4242
4243                         }
4244                 }
4245         }
4246 }
4247
4248 function lose_follower($importer,$contact,$datarray,$item) {
4249
4250         if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4251                 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4252                         intval(CONTACT_IS_SHARING),
4253                         intval($contact['id'])
4254                 );
4255         }
4256         else {
4257                 contact_remove($contact['id']);
4258         }
4259 }
4260
4261 function lose_sharer($importer,$contact,$datarray,$item) {
4262
4263         if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4264                 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4265                         intval(CONTACT_IS_FOLLOWER),
4266                         intval($contact['id'])
4267                 );
4268         }
4269         else {
4270                 contact_remove($contact['id']);
4271         }
4272 }
4273
4274
4275 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4276
4277         $a = get_app();
4278
4279         if(is_array($importer)) {
4280                 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4281                         intval($importer['uid'])
4282                 );
4283         }
4284
4285         // Diaspora has different message-ids in feeds than they do
4286         // through the direct Diaspora protocol. If we try and use
4287         // the feed, we'll get duplicates. So don't.
4288
4289         if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4290                 return;
4291
4292         $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4293
4294         // Use a single verify token, even if multiple hubs
4295
4296         $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4297
4298         $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4299
4300         logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: '  . $push_url . ' with verifier ' . $verify_token);
4301
4302         if(! strlen($contact['hub-verify'])) {
4303                 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4304                         dbesc($verify_token),
4305                         intval($contact['id'])
4306                 );
4307         }
4308
4309         post_url($url,$params);
4310
4311         logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4312
4313         return;
4314
4315 }
4316
4317
4318 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4319         $o = '';
4320         if(! $tag)
4321                 return $o;
4322         $name = xmlify($name);
4323         $uri = xmlify($uri);
4324         $h = intval($h);
4325         $w = intval($w);
4326         $photo = xmlify($photo);
4327
4328
4329         $o .= "<$tag>\r\n";
4330         $o .= "\t<name>$name</name>\r\n";
4331         $o .= "\t<uri>$uri</uri>\r\n";
4332         $o .= "\t".'<link rel="photo"  type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4333         $o .= "\t".'<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4334
4335         if ($tag == "author") {
4336                 $r = q("SELECT `profile`.`locality`, `profile`.`region`, `profile`.`country-name`,
4337                                 `profile`.`name`, `profile`.`pub_keywords`, `profile`.`about`,
4338                                 `profile`.`homepage`,`contact`.`nick` FROM `profile`
4339                                 INNER JOIN `contact` ON `contact`.`uid` = `profile`.`uid`
4340                                 INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
4341                                 WHERE `profile`.`is-default` AND `contact`.`self` AND
4342                                         NOT `user`.`hidewall` AND `contact`.`nurl`='%s'",
4343                         dbesc(normalise_link($uri)));
4344                 if ($r) {
4345                         $location = '';
4346                         if($r[0]['locality'])
4347                                 $location .= $r[0]['locality'];
4348                         if($r[0]['region']) {
4349                                 if($location)
4350                                         $location .= ', ';
4351                                 $location .= $r[0]['region'];
4352                         }
4353                         if($r[0]['country-name']) {
4354                                 if($location)
4355                                         $location .= ', ';
4356                                 $location .= $r[0]['country-name'];
4357                         }
4358
4359                         $o .= "\t<poco:preferredUsername>".xmlify($r[0]["nick"])."</poco:preferredUsername>\r\n";
4360                         $o .= "\t<poco:displayName>".xmlify($r[0]["name"])."</poco:displayName>\r\n";
4361                         $o .= "\t<poco:note>".xmlify($r[0]["about"])."</poco:note>\r\n";
4362                         $o .= "\t<poco:address>\r\n";
4363                         $o .= "\t\t<poco:formatted>".xmlify($location)."</poco:formatted>\r\n";
4364                         $o .= "\t</poco:address>\r\n";
4365                         $o .= "\t<poco:urls>\r\n";
4366                         $o .= "\t<poco:type>homepage</poco:type>\r\n";
4367                         $o .= "\t\t<poco:value>".xmlify($r[0]["homepage"])."</poco:value>\r\n";
4368                         $o .= "\t\t<poco:primary>true</poco:primary>\r\n";
4369                         $o .= "\t</poco:urls>\r\n";
4370                 }
4371         }
4372
4373         call_hooks('atom_author', $o);
4374
4375         $o .= "</$tag>\r\n";
4376         return $o;
4377 }
4378
4379 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4380
4381         $a = get_app();
4382
4383         if(! $item['parent'])
4384                 return;
4385
4386         if($item['deleted'])
4387                 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4388
4389
4390         if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4391                 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4392         else
4393                 $body = $item['body'];
4394
4395
4396         $o = "\r\n\r\n<entry>\r\n";
4397
4398         if(is_array($author))
4399                 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4400         else
4401                 $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']));
4402         if(strlen($item['owner-name']))
4403                 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4404
4405         if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4406                 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4407                 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' .  xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
4408         }
4409
4410         $htmlbody = $body;
4411
4412         if ($item['title'] != "")
4413                 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4414
4415         //$htmlbody = bbcode(bb_remove_share_information($htmlbody), false, false, 7);
4416         $htmlbody = bbcode($htmlbody, false, false, 7);
4417
4418         $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4419         $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4420         $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4421         $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4422         $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4423         $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4424         $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
4425
4426
4427         $o .= '<status_net notice_id="'.$item['id'].'"></status_net>'."\r\n";
4428
4429         if($comment)
4430                 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4431
4432         if($item['location']) {
4433                 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4434                 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4435         }
4436
4437         if($item['coord'])
4438                 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4439
4440         if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4441                 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4442
4443         if($item['extid'])
4444                 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4445         if($item['bookmark'])
4446                 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4447
4448         if($item['app'])
4449                 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4450
4451         if($item['guid'])
4452                 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4453
4454         if($item['signed_text']) {
4455                 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4456                 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4457         }
4458
4459         $verb = construct_verb($item);
4460         $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4461         $actobj = construct_activity_object($item);
4462         if(strlen($actobj))
4463                 $o .= $actobj;
4464         $actarg = construct_activity_target($item);
4465         if(strlen($actarg))
4466                 $o .= $actarg;
4467
4468         $tags = item_getfeedtags($item);
4469         if(count($tags)) {
4470                 foreach($tags as $t)
4471                         if (($type != 'html') OR ($t[0] != "@"))
4472                                 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4473         }
4474
4475         // To-Do:
4476         // To support these elements, the API needs to be enhanced
4477         //$o .= '<link rel="ostatus:conversation" href="'.xmlify($a->get_baseurl().'/display/'.$owner['nickname'].'/'.$item['parent']).'"/>'."\r\n";
4478         //$o .= "\t".'<link rel="self" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4479         //$o .= "\t".'<link rel="edit" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4480
4481         $o .= item_get_attachment($item);
4482
4483         $o .= item_getfeedattach($item);
4484
4485         $mentioned = get_mentions($item);
4486         if($mentioned)
4487                 $o .= $mentioned;
4488
4489         call_hooks('atom_entry', $o);
4490
4491         $o .= '</entry>' . "\r\n";
4492
4493         return $o;
4494 }
4495
4496 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4497
4498         if(get_config('system','disable_embedded'))
4499                 return $s;
4500
4501         $a = get_app();
4502
4503         logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4504         $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4505
4506         $orig_body = $s;
4507         $new_body = '';
4508
4509         $img_start = strpos($orig_body, '[img');
4510         $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4511         $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4512         while( ($img_st_close !== false) && ($img_len !== false) ) {
4513
4514                 $img_st_close++; // make it point to AFTER the closing bracket
4515                 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4516
4517                 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4518
4519
4520                 if(stristr($image , $site . '/photo/')) {
4521                         // Only embed locally hosted photos
4522                         $replace = false;
4523                         $i = basename($image);
4524                         $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4525                         $x = strpos($i,'-');
4526
4527                         if($x) {
4528                                 $res = substr($i,$x+1);
4529                                 $i = substr($i,0,$x);
4530                                 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4531                                         dbesc($i),
4532                                         intval($res),
4533                                         intval($uid)
4534                                 );
4535                                 if($r) {
4536
4537                                         // Check to see if we should replace this photo link with an embedded image
4538                                         // 1. No need to do so if the photo is public
4539                                         // 2. If there's a contact-id provided, see if they're in the access list
4540                                         //    for the photo. If so, embed it.
4541                                         // 3. Otherwise, if we have an item, see if the item permissions match the photo
4542                                         //    permissions, regardless of order but first check to see if they're an exact
4543                                         //    match to save some processing overhead.
4544
4545                                         if(has_permissions($r[0])) {
4546                                                 if($cid) {
4547                                                         $recips = enumerate_permissions($r[0]);
4548                                                         if(in_array($cid, $recips)) {
4549                                                                 $replace = true;
4550                                                         }
4551                                                 }
4552                                                 elseif($item) {
4553                                                         if(compare_permissions($item,$r[0]))
4554                                                                 $replace = true;
4555                                                 }
4556                                         }
4557                                         if($replace) {
4558                                                 $data = $r[0]['data'];
4559                                                 $type = $r[0]['type'];
4560
4561                                                 // If a custom width and height were specified, apply before embedding
4562                                                 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4563                                                         logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4564
4565                                                         $width = intval($match[1]);
4566                                                         $height = intval($match[2]);
4567
4568                                                         $ph = new Photo($data, $type);
4569                                                         if($ph->is_valid()) {
4570                                                                 $ph->scaleImage(max($width, $height));
4571                                                                 $data = $ph->imageString();
4572                                                                 $type = $ph->getType();
4573                                                         }
4574                                                 }
4575
4576                                                 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4577                                                 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4578                                                 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4579                                         }
4580                                 }
4581                         }
4582                 }
4583
4584                 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4585                 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4586                 if($orig_body === false)
4587                         $orig_body = '';
4588
4589                 $img_start = strpos($orig_body, '[img');
4590                 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4591                 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4592         }
4593
4594         $new_body = $new_body . $orig_body;
4595
4596         return($new_body);
4597 }
4598
4599
4600 function has_permissions($obj) {
4601         if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4602                 return true;
4603         return false;
4604 }
4605
4606 function compare_permissions($obj1,$obj2) {
4607         // first part is easy. Check that these are exactly the same.
4608         if(($obj1['allow_cid'] == $obj2['allow_cid'])
4609                 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4610                 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4611                 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4612                 return true;
4613
4614         // This is harder. Parse all the permissions and compare the resulting set.
4615
4616         $recipients1 = enumerate_permissions($obj1);
4617         $recipients2 = enumerate_permissions($obj2);
4618         sort($recipients1);
4619         sort($recipients2);
4620         if($recipients1 == $recipients2)
4621                 return true;
4622         return false;
4623 }
4624
4625 // returns an array of contact-ids that are allowed to see this object
4626
4627 function enumerate_permissions($obj) {
4628         require_once('include/group.php');
4629         $allow_people = expand_acl($obj['allow_cid']);
4630         $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4631         $deny_people  = expand_acl($obj['deny_cid']);
4632         $deny_groups  = expand_groups(expand_acl($obj['deny_gid']));
4633         $recipients   = array_unique(array_merge($allow_people,$allow_groups));
4634         $deny         = array_unique(array_merge($deny_people,$deny_groups));
4635         $recipients   = array_diff($recipients,$deny);
4636         return $recipients;
4637 }
4638
4639 function item_getfeedtags($item) {
4640         $ret = array();
4641         $matches = false;
4642         $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4643         if($cnt) {
4644                 for($x = 0; $x < $cnt; $x ++) {
4645                         if($matches[1][$x])
4646                                 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4647                 }
4648         }
4649         $matches = false;
4650         $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4651         if($cnt) {
4652                 for($x = 0; $x < $cnt; $x ++) {
4653                         if($matches[1][$x])
4654                                 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4655                 }
4656         }
4657         return $ret;
4658 }
4659
4660 function item_get_attachment($item) {
4661         $o = "";
4662         $siteinfo = get_attached_data($item["body"]);
4663
4664         switch($siteinfo["type"]) {
4665                 case 'link':
4666                         $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4667                         break;
4668                 case 'photo':
4669                         $imgdata = get_photo_info($siteinfo["image"]);
4670                         $o = '<link rel="enclosure" href="'.xmlify($siteinfo["image"]).'" type="'.$imgdata["mime"].'" length="'.$imgdata["size"].'"/>'."\r\n";
4671                         break;
4672                 case 'video':
4673                         $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4674                         break;
4675                 default:
4676                         break;
4677         }
4678
4679         return $o;
4680 }
4681
4682 function item_getfeedattach($item) {
4683         $ret = '';
4684         $arr = explode('[/attach],',$item['attach']);
4685         if(count($arr)) {
4686                 foreach($arr as $r) {
4687                         $matches = false;
4688                         $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4689                         if($cnt) {
4690                                 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4691                                 if(intval($matches[2]))
4692                                         $ret .= 'length="' . intval($matches[2]) . '" ';
4693                                 if($matches[4] !== ' ')
4694                                         $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4695                                 $ret .= ' />' . "\r\n";
4696                         }
4697                 }
4698         }
4699         return $ret;
4700 }
4701
4702
4703
4704 function item_expire($uid, $days, $network = "", $force = false) {
4705
4706         if((! $uid) || ($days < 1))
4707                 return;
4708
4709         // $expire_network_only = save your own wall posts
4710         // and just expire conversations started by others
4711
4712         $expire_network_only = get_pconfig($uid,'expire','network_only');
4713         $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4714
4715         if ($network != "") {
4716                 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4717                 // There is an index "uid_network_received" but not "uid_network_created"
4718                 // This avoids the creation of another index just for one purpose.
4719                 // And it doesn't really matter wether to look at "received" or "created"
4720                 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4721         } else
4722                 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4723
4724         $r = q("SELECT * FROM `item`
4725                 WHERE `uid` = %d $range
4726                 AND `id` = `parent`
4727                 $sql_extra
4728                 AND `deleted` = 0",
4729                 intval($uid),
4730                 intval($days)
4731         );
4732
4733         if(! count($r))
4734                 return;
4735
4736         $expire_items = get_pconfig($uid, 'expire','items');
4737         $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4738
4739         // Forcing expiring of items - but not notes and marked items
4740         if ($force)
4741                 $expire_items = true;
4742
4743         $expire_notes = get_pconfig($uid, 'expire','notes');
4744         $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4745
4746         $expire_starred = get_pconfig($uid, 'expire','starred');
4747         $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4748
4749         $expire_photos = get_pconfig($uid, 'expire','photos');
4750         $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4751
4752         logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4753
4754         foreach($r as $item) {
4755
4756                 // don't expire filed items
4757
4758                 if(strpos($item['file'],'[') !== false)
4759                         continue;
4760
4761                 // Only expire posts, not photos and photo comments
4762
4763                 if($expire_photos==0 && strlen($item['resource-id']))
4764                         continue;
4765                 if($expire_starred==0 && intval($item['starred']))
4766                         continue;
4767                 if($expire_notes==0 && $item['type']=='note')
4768                         continue;
4769                 if($expire_items==0 && $item['type']!='note')
4770                         continue;
4771
4772                 drop_item($item['id'],false);
4773         }
4774
4775         proc_run('php',"include/notifier.php","expire","$uid");
4776
4777 }
4778
4779
4780 function drop_items($items) {
4781         $uid = 0;
4782
4783         if(! local_user() && ! remote_user())
4784                 return;
4785
4786         if(count($items)) {
4787                 foreach($items as $item) {
4788                         $owner = drop_item($item,false);
4789                         if($owner && ! $uid)
4790                                 $uid = $owner;
4791                 }
4792         }
4793
4794         // multiple threads may have been deleted, send an expire notification
4795
4796         if($uid)
4797                 proc_run('php',"include/notifier.php","expire","$uid");
4798 }
4799
4800
4801 function drop_item($id,$interactive = true) {
4802
4803         $a = get_app();
4804
4805         // locate item to be deleted
4806
4807         $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4808                 intval($id)
4809         );
4810
4811         if(! count($r)) {
4812                 if(! $interactive)
4813                         return 0;
4814                 notice( t('Item not found.') . EOL);
4815                 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4816         }
4817
4818         $item = $r[0];
4819
4820         $owner = $item['uid'];
4821
4822         $cid = 0;
4823
4824         // check if logged in user is either the author or owner of this item
4825
4826         if(is_array($_SESSION['remote'])) {
4827                 foreach($_SESSION['remote'] as $visitor) {
4828                         if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4829                                 $cid = $visitor['cid'];
4830                                 break;
4831                         }
4832                 }
4833         }
4834
4835
4836         if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4837
4838                 // Check if we should do HTML-based delete confirmation
4839                 if($_REQUEST['confirm']) {
4840                         // <form> can't take arguments in its "action" parameter
4841                         // so add any arguments as hidden inputs
4842                         $query = explode_querystring($a->query_string);
4843                         $inputs = array();
4844                         foreach($query['args'] as $arg) {
4845                                 if(strpos($arg, 'confirm=') === false) {
4846                                         $arg_parts = explode('=', $arg);
4847                                         $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4848                                 }
4849                         }
4850
4851                         return replace_macros(get_markup_template('confirm.tpl'), array(
4852                                 '$method' => 'get',
4853                                 '$message' => t('Do you really want to delete this item?'),
4854                                 '$extra_inputs' => $inputs,
4855                                 '$confirm' => t('Yes'),
4856                                 '$confirm_url' => $query['base'],
4857                                 '$confirm_name' => 'confirmed',
4858                                 '$cancel' => t('Cancel'),
4859                         ));
4860                 }
4861                 // Now check how the user responded to the confirmation query
4862                 if($_REQUEST['canceled']) {
4863                         goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4864                 }
4865
4866                 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4867                 // delete the item
4868
4869                 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4870                         dbesc(datetime_convert()),
4871                         dbesc(datetime_convert()),
4872                         intval($item['id'])
4873                 );
4874                 create_tags_from_item($item['id']);
4875                 create_files_from_item($item['id']);
4876                 delete_thread($item['id'], $item['parent-uri']);
4877
4878                 // clean up categories and tags so they don't end up as orphans
4879
4880                 $matches = false;
4881                 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4882                 if($cnt) {
4883                         foreach($matches as $mtch) {
4884                                 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4885                         }
4886                 }
4887
4888                 $matches = false;
4889
4890                 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4891                 if($cnt) {
4892                         foreach($matches as $mtch) {
4893                                 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4894                         }
4895                 }
4896
4897                 // If item is a link to a photo resource, nuke all the associated photos
4898                 // (visitors will not have photo resources)
4899                 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4900                 // generate a resource-id and therefore aren't intimately linked to the item.
4901
4902                 if(strlen($item['resource-id'])) {
4903                         q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4904                                 dbesc($item['resource-id']),
4905                                 intval($item['uid'])
4906                         );
4907                         // ignore the result
4908                 }
4909
4910                 // If item is a link to an event, nuke the event record.
4911
4912                 if(intval($item['event-id'])) {
4913                         q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4914                                 intval($item['event-id']),
4915                                 intval($item['uid'])
4916                         );
4917                         // ignore the result
4918                 }
4919
4920                 // If item has attachments, drop them
4921
4922                 foreach(explode(",",$item['attach']) as $attach){
4923                         preg_match("|attach/(\d+)|", $attach, $matches);
4924                         q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4925                                 intval($matches[1]),
4926                                 local_user()
4927                         );
4928                         // ignore the result
4929                 }
4930
4931
4932                 // clean up item_id and sign meta-data tables
4933
4934                 /*
4935                 // Old code - caused very long queries and warning entries in the mysql logfiles:
4936
4937                 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4938                         intval($item['id']),
4939                         intval($item['uid'])
4940                 );
4941
4942                 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4943                         intval($item['id']),
4944                         intval($item['uid'])
4945                 );
4946                 */
4947
4948                 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4949
4950                 // Creating list of parents
4951                 $r = q("select id from item where parent = %d and uid = %d",
4952                         intval($item['id']),
4953                         intval($item['uid'])
4954                 );
4955
4956                 $parentid = "";
4957
4958                 foreach ($r AS $row) {
4959                         if ($parentid != "")
4960                                 $parentid .= ", ";
4961
4962                         $parentid .= $row["id"];
4963                 }
4964
4965                 // Now delete them
4966                 if ($parentid != "") {
4967                         $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4968
4969                         $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4970                 }
4971
4972                 // If it's the parent of a comment thread, kill all the kids
4973
4974                 if($item['uri'] == $item['parent-uri']) {
4975                         $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4976                                 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4977                                 dbesc(datetime_convert()),
4978                                 dbesc(datetime_convert()),
4979                                 dbesc($item['parent-uri']),
4980                                 intval($item['uid'])
4981                         );
4982                         create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4983                         create_files_from_itemuri($item['parent-uri'], $item['uid']);
4984                         delete_thread_uri($item['parent-uri'], $item['uid']);
4985                         // ignore the result
4986                 }
4987                 else {
4988                         // ensure that last-child is set in case the comment that had it just got wiped.
4989                         q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4990                                 dbesc(datetime_convert()),
4991                                 dbesc($item['parent-uri']),
4992                                 intval($item['uid'])
4993                         );
4994                         // who is the last child now?
4995                         $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",
4996                                 dbesc($item['parent-uri']),
4997                                 intval($item['uid'])
4998                         );
4999                         if(count($r)) {
5000                                 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
5001                                         intval($r[0]['id'])
5002                                 );
5003                         }
5004
5005                         // Add a relayable_retraction signature for Diaspora.
5006                         store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
5007                 }
5008
5009                 $drop_id = intval($item['id']);
5010
5011                 // send the notification upstream/downstream as the case may be
5012
5013                 proc_run('php',"include/notifier.php","drop","$drop_id");
5014
5015                 if(! $interactive)
5016                         return $owner;
5017                 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5018                 //NOTREACHED
5019         }
5020         else {
5021                 if(! $interactive)
5022                         return 0;
5023                 notice( t('Permission denied.') . EOL);
5024                 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5025                 //NOTREACHED
5026         }
5027
5028 }
5029
5030
5031 function first_post_date($uid,$wall = false) {
5032         $r = q("select id, created from item
5033                 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
5034                 and id = parent
5035                 order by created asc limit 1",
5036                 intval($uid),
5037                 intval($wall ? 1 : 0)
5038         );
5039         if(count($r)) {
5040 //              logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
5041                 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
5042         }
5043         return false;
5044 }
5045
5046 /* modified posted_dates() {below} to arrange the list in years */
5047 function list_post_dates($uid, $wall) {
5048         $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5049
5050         $dthen = first_post_date($uid, $wall);
5051         if(! $dthen)
5052                 return array();
5053
5054         // Set the start and end date to the beginning of the month
5055         $dnow = substr($dnow,0,8).'01';
5056         $dthen = substr($dthen,0,8).'01';
5057
5058         $ret = array();
5059
5060         // Starting with the current month, get the first and last days of every
5061         // month down to and including the month of the first post
5062         while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5063                 $dyear = intval(substr($dnow,0,4));
5064                 $dstart = substr($dnow,0,8) . '01';
5065                 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5066                 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5067                 $end_month = datetime_convert('','',$dend,'Y-m-d');
5068                 $str = day_translate(datetime_convert('','',$dnow,'F'));
5069                 if(! $ret[$dyear])
5070                         $ret[$dyear] = array();
5071                 $ret[$dyear][] = array($str,$end_month,$start_month);
5072                 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5073         }
5074         return $ret;
5075 }
5076
5077 function posted_dates($uid,$wall) {
5078         $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5079
5080         $dthen = first_post_date($uid,$wall);
5081         if(! $dthen)
5082                 return array();
5083
5084         // Set the start and end date to the beginning of the month
5085         $dnow = substr($dnow,0,8).'01';
5086         $dthen = substr($dthen,0,8).'01';
5087
5088         $ret = array();
5089         // Starting with the current month, get the first and last days of every
5090         // month down to and including the month of the first post
5091         while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5092                 $dstart = substr($dnow,0,8) . '01';
5093                 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5094                 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5095                 $end_month = datetime_convert('','',$dend,'Y-m-d');
5096                 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
5097                 $ret[] = array($str,$end_month,$start_month);
5098                 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5099         }
5100         return $ret;
5101 }
5102
5103
5104 function posted_date_widget($url,$uid,$wall) {
5105         $o = '';
5106
5107         if(! feature_enabled($uid,'archives'))
5108                 return $o;
5109
5110         // For former Facebook folks that left because of "timeline"
5111
5112 /*      if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
5113                 return $o;*/
5114
5115         $visible_years = get_pconfig($uid,'system','archive_visible_years');
5116         if(! $visible_years)
5117                 $visible_years = 5;
5118
5119         $ret = list_post_dates($uid,$wall);
5120
5121         if(! count($ret))
5122                 return $o;
5123
5124         $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
5125         $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
5126
5127         $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
5128                 '$title' => t('Archives'),
5129                 '$size' => $visible_years,
5130                 '$cutoff_year' => $cutoff_year,
5131                 '$cutoff' => $cutoff,
5132                 '$url' => $url,
5133                 '$dates' => $ret,
5134                 '$showmore' => t('show more')
5135
5136         ));
5137         return $o;
5138 }
5139
5140 function store_diaspora_retract_sig($item, $user, $baseurl) {
5141         // Note that we can't add a target_author_signature
5142         // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
5143         // the comment, that means we're the home of the post, and Diaspora will only
5144         // check the parent_author_signature of retractions that it doesn't have to relay further
5145         //
5146         // I don't think this function gets called for an "unlike," but I'll check anyway
5147
5148         $enabled = intval(get_config('system','diaspora_enabled'));
5149         if(! $enabled) {
5150                 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
5151                 return;
5152         }
5153
5154         logger('drop_item: storing diaspora retraction signature');
5155
5156         $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
5157
5158         if(local_user() == $item['uid']) {
5159
5160                 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
5161                 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
5162         }
5163         else {
5164                 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
5165                         $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
5166                 );
5167                 if(count($r)) {
5168                         // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
5169                         // only handles DFRN deletes
5170                         $handle_baseurl_start = strpos($r['url'],'://') + 3;
5171                         $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
5172                         $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
5173                         $authorsig = '';
5174                 }
5175         }
5176
5177         if(isset($handle))
5178                 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
5179                         intval($item['id']),
5180                         dbesc($signed_text),
5181                         dbesc($authorsig),
5182                         dbesc($handle)
5183                 );
5184
5185         return;
5186 }