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