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