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