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