]> git.mxchange.org Git - friendica.git/blob - include/items.php
Merge pull request #803 from annando/master
[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                                         $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`parent-uri` = '%s' OR `thr-parent` = '%s') limit 1",
2112                                                 intval($datarray['uid']),
2113                                                 intval($datarray['contact-id']),
2114                                                 dbesc($datarray['verb']),
2115                                                 dbesc($parent_uri),
2116                                                 dbesc($parent_uri)
2117                                         );
2118                                         if($r && count($r))
2119                                                 continue;
2120                                 }
2121
2122                                 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2123                                         $xo = parse_xml_string($datarray['object'],false);
2124                                         $xt = parse_xml_string($datarray['target'],false);
2125
2126                                         if($xt->type == ACTIVITY_OBJ_NOTE) {
2127                                                 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2128                                                         dbesc($xt->id),
2129                                                         intval($importer['importer_uid'])
2130                                                 );
2131                                                 if(! count($r))
2132                                                         continue;
2133
2134                                                 // extract tag, if not duplicate, add to parent item
2135                                                 if($xo->id && $xo->content) {
2136                                                         $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2137                                                         if(! (stristr($r[0]['tag'],$newtag))) {
2138                                                                 q("UPDATE item SET tag = '%s' WHERE id = %d",
2139                                                                         dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2140                                                                         intval($r[0]['id'])
2141                                                                 );
2142                                                                 create_tags_from_item($r[0]['id']);
2143                                                         }
2144                                                 }
2145                                         }
2146                                 }
2147
2148                                 $r = item_store($datarray,$force_parent);
2149                                 continue;
2150                         }
2151
2152                         else {
2153
2154                                 // Head post of a conversation. Have we seen it? If not, import it.
2155
2156                                 $item_id  = $item->get_id();
2157
2158                                 $datarray = get_atom_elements($feed,$item);
2159
2160                                 if(is_array($contact)) {
2161                                         if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2162                                                 $datarray['author-name'] = $contact['name'];
2163                                         if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2164                                                 $datarray['author-link'] = $contact['url'];
2165                                         if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2166                                                 $datarray['author-avatar'] = $contact['thumb'];
2167                                 }
2168
2169                                 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2170                                         logger('consume_feed: no author information! ' . print_r($datarray,true));
2171                                         continue;
2172                                 }
2173
2174                                 // special handling for events
2175
2176                                 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2177                                         $ev = bbtoevent($datarray['body']);
2178                                         if(x($ev,'desc') && x($ev,'start')) {
2179                                                 $ev['uid'] = $importer['uid'];
2180                                                 $ev['uri'] = $item_id;
2181                                                 $ev['edited'] = $datarray['edited'];
2182                                                 $ev['private'] = $datarray['private'];
2183
2184                                                 if(is_array($contact))
2185                                                         $ev['cid'] = $contact['id'];
2186                                                 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2187                                                         dbesc($item_id),
2188                                                         intval($importer['uid'])
2189                                                 );
2190                                                 if(count($r))
2191                                                         $ev['id'] = $r[0]['id'];
2192                                                 $xyz = event_store($ev);
2193                                                 continue;
2194                                         }
2195                                 }
2196
2197                                 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2198                                         if(strlen($datarray['title']))
2199                                                 unset($datarray['title']);
2200                                         $datarray['last-child'] = 1;
2201                                 }
2202
2203
2204                                 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2205                                         dbesc($item_id),
2206                                         intval($importer['uid'])
2207                                 );
2208
2209                                 // Update content if 'updated' changes
2210
2211                                 if(count($r)) {
2212                                         if (edited_timestamp_is_newer($r[0], $datarray)) {  
2213
2214                                                 // do not accept (ignore) an earlier edit than one we currently have.
2215                                                 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2216                                                         continue;
2217
2218                                                 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2219                                                         dbesc($datarray['title']),
2220                                                         dbesc($datarray['body']),
2221                                                         dbesc($datarray['tag']),
2222                                                         dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2223                                                         dbesc($item_id),
2224                                                         intval($importer['uid'])
2225                                                 );
2226                                                 create_tags_from_itemuri($item_id, $importer['uid']);
2227                                         }
2228
2229                                         // update last-child if it changes
2230
2231                                         $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2232                                         if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2233                                                 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2234                                                         intval($allow[0]['data']),
2235                                                         dbesc(datetime_convert()),
2236                                                         dbesc($item_id),
2237                                                         intval($importer['uid'])
2238                                                 );
2239                                         }
2240                                         continue;
2241                                 }
2242
2243                                 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2244                                         logger('consume-feed: New follower');
2245                                         new_follower($importer,$contact,$datarray,$item);
2246                                         return;
2247                                 }
2248                                 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW))  {
2249                                         lose_follower($importer,$contact,$datarray,$item);
2250                                         return;
2251                                 }
2252
2253                                 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2254                                         logger('consume-feed: New friend request');
2255                                         new_follower($importer,$contact,$datarray,$item,true);
2256                                         return;
2257                                 }
2258                                 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND))  {
2259                                         lose_sharer($importer,$contact,$datarray,$item);
2260                                         return;
2261                                 }
2262
2263
2264                                 if(! is_array($contact))
2265                                         return;
2266
2267
2268                                 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2269                                                 // one way feed - no remote comment ability
2270                                                 $datarray['last-child'] = 0;
2271                                 }
2272                                 if($contact['network'] === NETWORK_FEED)
2273                                         $datarray['private'] = 2;
2274
2275                                 // This is my contact on another system, but it's really me.
2276                                 // Turn this into a wall post.
2277
2278                                 if($contact['remote_self']) {
2279                                         $datarray['wall'] = 1;
2280                                         if($contact['network'] === NETWORK_FEED) {
2281                                                 $datarray['private'] = 0;
2282                                         }
2283                                 }
2284
2285                                 $datarray['parent-uri'] = $item_id;
2286                                 $datarray['uid'] = $importer['uid'];
2287                                 $datarray['contact-id'] = $contact['id'];
2288
2289                                 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2290                                         // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery, 
2291                                         // but otherwise there's a possible data mixup on the sender's system.
2292                                         // the tgroup delivery code called from item_store will correct it if it's a forum,
2293                                         // but we're going to unconditionally correct it here so that the post will always be owned by our contact. 
2294                                         logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2295                                         $datarray['owner-name']   = $contact['name'];
2296                                         $datarray['owner-link']   = $contact['url'];
2297                                         $datarray['owner-avatar'] = $contact['thumb'];
2298                                 }
2299
2300                                 // We've allowed "followers" to reach this point so we can decide if they are 
2301                                 // posting an @-tag delivery, which followers are allowed to do for certain
2302                                 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it. 
2303
2304                                 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2305                                         continue;
2306
2307
2308                                 $r = item_store($datarray);
2309                                 continue;
2310
2311                         }
2312                 }
2313         }
2314 }
2315
2316 function local_delivery($importer,$data) {
2317         $a = get_app();
2318
2319     logger(__function__, LOGGER_TRACE);
2320
2321         if($importer['readonly']) {
2322                 // We aren't receiving stuff from this person. But we will quietly ignore them
2323                 // rather than a blatant "go away" message.
2324                 logger('local_delivery: ignoring');
2325                 return 0;
2326                 //NOTREACHED
2327         }
2328
2329         // Consume notification feed. This may differ from consuming a public feed in several ways
2330         // - might contain email or friend suggestions
2331         // - might contain remote followup to our message
2332         //              - in which case we need to accept it and then notify other conversants
2333         // - we may need to send various email notifications
2334
2335         $feed = new SimplePie();
2336         $feed->set_raw_data($data);
2337         $feed->enable_order_by_date(false);
2338         $feed->init();
2339
2340
2341         if($feed->error())
2342                 logger('local_delivery: Error parsing XML: ' . $feed->error());
2343
2344
2345         // Check at the feed level for updated contact name and/or photo
2346
2347         $name_updated  = '';
2348         $new_name = '';
2349         $photo_timestamp = '';
2350         $photo_url = '';
2351
2352
2353         $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2354
2355 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2356 //      if(! $rawtags)
2357 //              $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2358
2359         if($rawtags) {
2360                 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2361                 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2362                         $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2363                         $new_name = $elems['name'][0]['data'];
2364                 }
2365                 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2366                         $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2367                         $photo_url = $elems['link'][0]['attribs']['']['href'];
2368                 }
2369         }
2370
2371         if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2372                 logger('local_delivery: Updating photo for ' . $importer['name']);
2373                 require_once("include/Photo.php");
2374                 $photo_failure = false;
2375                 $have_photo = false;
2376
2377                 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2378                         intval($importer['id']),
2379                         intval($importer['importer_uid'])
2380                 );
2381                 if(count($r)) {
2382                         $resource_id = $r[0]['resource-id'];
2383                         $have_photo = true;
2384                 }
2385                 else {
2386                         $resource_id = photo_new_resource();
2387                 }
2388
2389                 $img_str = fetch_url($photo_url,true);
2390                 // guess mimetype from headers or filename
2391                 $type = guess_image_type($photo_url,true);
2392
2393
2394                 $img = new Photo($img_str, $type);
2395                 if($img->is_valid()) {
2396                         if($have_photo) {
2397                                 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2398                                         dbesc($resource_id),
2399                                         intval($importer['id']),
2400                                         intval($importer['importer_uid'])
2401                                 );
2402                         }
2403
2404                         $img->scaleImageSquare(175);
2405
2406                         $hash = $resource_id;
2407                         $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2408
2409                         $img->scaleImage(80);
2410                         $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2411
2412                         $img->scaleImage(48);
2413                         $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2414
2415                         $a = get_app();
2416
2417                         q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2418                                 WHERE `uid` = %d AND `id` = %d",
2419                                 dbesc(datetime_convert()),
2420                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2421                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2422                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2423                                 intval($importer['importer_uid']),
2424                                 intval($importer['id'])
2425                         );
2426                 }
2427         }
2428
2429         if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2430                 $r = q("select * from contact where uid = %d and id = %d limit 1",
2431                         intval($importer['importer_uid']),
2432                         intval($importer['id'])
2433                 );
2434
2435                 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2436                         dbesc(notags(trim($new_name))),
2437                         dbesc(datetime_convert()),
2438                         intval($importer['importer_uid']),
2439                         intval($importer['id'])
2440                 );
2441
2442                 // do our best to update the name on content items
2443
2444                 if(count($r)) {
2445                         q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2446                                 dbesc(notags(trim($new_name))),
2447                                 dbesc($r[0]['name']),
2448                                 dbesc($r[0]['url']),
2449                                 intval($importer['importer_uid'])
2450                         );
2451                 }
2452         }
2453
2454
2455
2456         // Currently unsupported - needs a lot of work
2457         $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2458         if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2459                 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2460                 $newloc = array();
2461                 $newloc['uid'] = $importer['importer_uid'];
2462                 $newloc['cid'] = $importer['id'];
2463                 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2464                 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2465                 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2466                 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2467                 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2468                 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2469                 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2470                 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2471                 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2472                 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2473                 /** relocated user must have original key pair */
2474                 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2475                 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2476
2477         logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2478
2479         // update contact
2480         $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2481                     intval($importer['id']),
2482                                         intval($importer['importer_uid']));
2483                 if ($r === false)
2484                         return 1;
2485         $old = $r[0];
2486
2487         $x = q("UPDATE contact SET
2488                         name = '%s',
2489                         photo = '%s',
2490                         thumb = '%s',
2491                         micro = '%s',
2492                         url = '%s',
2493                         request = '%s',
2494                         confirm = '%s',
2495                         notify = '%s',
2496                         poll = '%s',
2497                         `site-pubkey` = '%s'
2498                 WHERE id=%d AND uid=%d;",
2499                     dbesc($newloc['name']),
2500                     dbesc($newloc['photo']),
2501                     dbesc($newloc['thumb']),
2502                     dbesc($newloc['micro']),
2503                     dbesc($newloc['url']),
2504                     dbesc($newloc['request']),
2505                     dbesc($newloc['confirm']),
2506                     dbesc($newloc['notify']),
2507                     dbesc($newloc['poll']),
2508                     dbesc($newloc['sitepubkey']),
2509                     intval($importer['id']),
2510                                         intval($importer['importer_uid']));
2511
2512         if ($x === false)
2513                         return 1;
2514         // update items
2515         $fields = array(
2516             'owner-link' => array($old['url'], $newloc['url']),
2517             'author-link' => array($old['url'], $newloc['url']),
2518             'owner-avatar' => array($old['photo'], $newloc['photo']),
2519             'author-avatar' => array($old['photo'], $newloc['photo']),
2520         );
2521         foreach ($fields as $n=>$f){
2522             $x = q("UPDATE item SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2523                         $n, dbesc($f[1]),
2524                         $n, dbesc($f[0]),
2525                         intval($importer['importer_uid']));
2526                         if ($x === false)
2527                                 return 1;
2528                 }
2529
2530                 // TODO
2531                 // merge with current record, current contents have priority
2532                 // update record, set url-updated
2533                 // update profile photos
2534                 // schedule a scan?
2535         return 0;
2536         }
2537
2538
2539         // handle friend suggestion notification
2540
2541         $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2542         if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2543                 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2544                 $fsugg = array();
2545                 $fsugg['uid'] = $importer['importer_uid'];
2546                 $fsugg['cid'] = $importer['id'];
2547                 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2548                 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2549                 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2550                 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2551                 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2552
2553                 // Does our member already have a friend matching this description?
2554
2555                 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2556                         dbesc($fsugg['name']),
2557                         dbesc(normalise_link($fsugg['url'])),
2558                         intval($fsugg['uid'])
2559                 );
2560                 if(count($r))
2561                         return 0;
2562
2563                 // Do we already have an fcontact record for this person?
2564
2565                 $fid = 0;
2566                 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2567                         dbesc($fsugg['url']),
2568                         dbesc($fsugg['name']),
2569                         dbesc($fsugg['request'])
2570                 );
2571                 if(count($r)) {
2572                         $fid = $r[0]['id'];
2573
2574                         // OK, we do. Do we already have an introduction for this person ?
2575                         $r = q("select id from intro where uid = %d and fid = %d limit 1",
2576                                 intval($fsugg['uid']),
2577                                 intval($fid)
2578                         );
2579                         if(count($r))
2580                                 return 0;
2581                 }
2582                 if(! $fid)
2583                         $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2584                         dbesc($fsugg['name']),
2585                         dbesc($fsugg['url']),
2586                         dbesc($fsugg['photo']),
2587                         dbesc($fsugg['request'])
2588                 );
2589                 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2590                         dbesc($fsugg['url']),
2591                         dbesc($fsugg['name']),
2592                         dbesc($fsugg['request'])
2593                 );
2594                 if(count($r)) {
2595                         $fid = $r[0]['id'];
2596                 }
2597                 // database record did not get created. Quietly give up.
2598                 else
2599                         return 0;
2600
2601
2602                 $hash = random_string();
2603
2604                 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2605                         VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2606                         intval($fsugg['uid']),
2607                         intval($fid),
2608                         intval($fsugg['cid']),
2609                         dbesc($fsugg['body']),
2610                         dbesc($hash),
2611                         dbesc(datetime_convert()),
2612                         intval(0)
2613                 );
2614
2615                 notification(array(
2616                         'type'         => NOTIFY_SUGGEST,
2617                         'notify_flags' => $importer['notify-flags'],
2618                         'language'     => $importer['language'],
2619                         'to_name'      => $importer['username'],
2620                         'to_email'     => $importer['email'],
2621                         'uid'          => $importer['importer_uid'],
2622                         'item'         => $fsugg,
2623                         'link'         => $a->get_baseurl() . '/notifications/intros',
2624                         'source_name'  => $importer['name'],
2625                         'source_link'  => $importer['url'],
2626                         'source_photo' => $importer['photo'],
2627                         'verb'         => ACTIVITY_REQ_FRIEND,
2628                         'otype'        => 'intro'
2629                 ));
2630
2631                 return 0;
2632         }
2633
2634         $ismail = false;
2635
2636         $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2637         if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2638
2639                 logger('local_delivery: private message received');
2640
2641                 $ismail = true;
2642                 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2643
2644                 $msg = array();
2645                 $msg['uid'] = $importer['importer_uid'];
2646                 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2647                 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2648                 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2649                 $msg['contact-id'] = $importer['id'];
2650                 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2651                 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2652                 $msg['seen'] = 0;
2653                 $msg['replied'] = 0;
2654                 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2655                 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2656                 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2657
2658                 dbesc_array($msg);
2659
2660                 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg)) 
2661                         . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2662
2663                 // send notifications.
2664
2665                 require_once('include/enotify.php');
2666
2667                 $notif_params = array(
2668                         'type' => NOTIFY_MAIL,
2669                         'notify_flags' => $importer['notify-flags'],
2670                         'language' => $importer['language'],
2671                         'to_name' => $importer['username'],
2672                         'to_email' => $importer['email'],
2673                         'uid' => $importer['importer_uid'],
2674                         'item' => $msg,
2675                         'source_name' => $msg['from-name'],
2676                         'source_link' => $importer['url'],
2677                         'source_photo' => $importer['thumb'],
2678                         'verb' => ACTIVITY_POST,
2679                         'otype' => 'mail'
2680                 );
2681
2682                 notification($notif_params);
2683                 return 0;
2684
2685                 // NOTREACHED
2686         }
2687
2688         $community_page = 0;
2689         $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2690         if($rawtags) {
2691                 $community_page = intval($rawtags[0]['data']);
2692         }
2693         if(intval($importer['forum']) != $community_page) {
2694                 q("update contact set forum = %d where id = %d",
2695                         intval($community_page),
2696                         intval($importer['id'])
2697                 );
2698                 $importer['forum'] = (string) $community_page;
2699         }
2700
2701         logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
2702
2703         // process any deleted entries
2704
2705         $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2706         if(is_array($del_entries) && count($del_entries)) {
2707                 foreach($del_entries as $dentry) {
2708                         $deleted = false;
2709                         if(isset($dentry['attribs']['']['ref'])) {
2710                                 $uri = $dentry['attribs']['']['ref'];
2711                                 $deleted = true;
2712                                 if(isset($dentry['attribs']['']['when'])) {
2713                                         $when = $dentry['attribs']['']['when'];
2714                                         $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2715                                 }
2716                                 else
2717                                         $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2718                         }
2719                         if($deleted) {
2720
2721                                 // check for relayed deletes to our conversation
2722
2723                                 $is_reply = false;
2724                                 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
2725                                         dbesc($uri),
2726                                         intval($importer['importer_uid'])
2727                                 );
2728                                 if(count($r)) {
2729                                         $parent_uri = $r[0]['parent-uri'];
2730                                         if($r[0]['id'] != $r[0]['parent'])
2731                                                 $is_reply = true;
2732                                 }
2733
2734                                 if($is_reply) {
2735                                         $community = false;
2736
2737                                         if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2738                                                 $sql_extra = '';
2739                                                 $community = true;
2740                                                 logger('local_delivery: possible community delete');
2741                                         }
2742                                         else
2743                                                 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2744  
2745                                         // was the top-level post for this reply written by somebody on this site? 
2746                                         // Specifically, the recipient? 
2747
2748                                         $is_a_remote_delete = false;
2749
2750                                         // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2751                                         $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`, 
2752                                                 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item` 
2753                                                 LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id` 
2754                                                 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2755                                                 AND `item`.`uid` = %d 
2756                                                 $sql_extra
2757                                                 LIMIT 1",
2758                                                 dbesc($parent_uri),
2759                                                 dbesc($parent_uri),
2760                                                 dbesc($parent_uri),
2761                                                 intval($importer['importer_uid'])
2762                                         );
2763                                         if($r && count($r))
2764                                                 $is_a_remote_delete = true;
2765
2766                                         // Does this have the characteristics of a community or private group comment?
2767                                         // If it's a reply to a wall post on a community/prvgroup page it's a 
2768                                         // valid community comment. Also forum_mode makes it valid for sure. 
2769                                         // If neither, it's not.
2770
2771                                         if($is_a_remote_delete && $community) {
2772                                                 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2773                                                         $is_a_remote_delete = false;
2774                                                         logger('local_delivery: not a community delete');
2775                                                 }
2776                                         }
2777
2778                                         if($is_a_remote_delete) {
2779                                                 logger('local_delivery: received remote delete');
2780                                         }
2781                                 }
2782
2783                                 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` left join contact on `item`.`contact-id` = `contact`.`id`
2784                                         WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2785                                         dbesc($uri),
2786                                         intval($importer['importer_uid']),
2787                                         intval($importer['id'])
2788                                 );
2789
2790                                 if(count($r)) {
2791                                         $item = $r[0];
2792
2793                                         if($item['deleted'])
2794                                                 continue;
2795
2796                                         logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2797
2798                                         if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2799                                                 $xo = parse_xml_string($item['object'],false);
2800                                                 $xt = parse_xml_string($item['target'],false);
2801
2802                                                 if($xt->type === ACTIVITY_OBJ_NOTE) {
2803                                                         $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2804                                                                 dbesc($xt->id),
2805                                                                 intval($importer['importer_uid'])
2806                                                         );
2807                                                         if(count($i)) {
2808
2809                                                                 // For tags, the owner cannot remove the tag on the author's copy of the post.
2810
2811                                                                 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2812                                                                 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2813                                                                 $author_copy = (($item['origin']) ? true : false);
2814
2815                                                                 if($owner_remove && $author_copy)
2816                                                                         continue;
2817                                                                 if($author_remove || $owner_remove) {
2818                                                                         $tags = explode(',',$i[0]['tag']);
2819                                                                         $newtags = array();
2820                                                                         if(count($tags)) {
2821                                                                                 foreach($tags as $tag)
2822                                                                                         if(trim($tag) !== trim($xo->body))
2823                                                                                                 $newtags[] = trim($tag);
2824                                                                         }
2825                                                                         q("update item set tag = '%s' where id = %d",
2826                                                                                 dbesc(implode(',',$newtags)),
2827                                                                                 intval($i[0]['id'])
2828                                                                         );
2829                                                                         create_tags_from_item($i[0]['id']);
2830                                                                 }
2831                                                         }
2832                                                 }
2833                                         }
2834
2835                                         if($item['uri'] == $item['parent-uri']) {
2836                                                 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2837                                                         `body` = '', `title` = ''
2838                                                         WHERE `parent-uri` = '%s' AND `uid` = %d",
2839                                                         dbesc($when),
2840                                                         dbesc(datetime_convert()),
2841                                                         dbesc($item['uri']),
2842                                                         intval($importer['importer_uid'])
2843                                                 );
2844                                                 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
2845                                         }
2846                                         else {
2847                                                 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2848                                                         `body` = '', `title` = ''
2849                                                         WHERE `uri` = '%s' AND `uid` = %d",
2850                                                         dbesc($when),
2851                                                         dbesc(datetime_convert()),
2852                                                         dbesc($uri),
2853                                                         intval($importer['importer_uid'])
2854                                                 );
2855                                                 create_tags_from_itemuri($uri, $importer['importer_uid']);
2856                                                 if($item['last-child']) {
2857                                                         // ensure that last-child is set in case the comment that had it just got wiped.
2858                                                         q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2859                                                                 dbesc(datetime_convert()),
2860                                                                 dbesc($item['parent-uri']),
2861                                                                 intval($item['uid'])
2862                                                         );
2863                                                         // who is the last child now?
2864                                                         $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
2865                                                                 ORDER BY `created` DESC LIMIT 1",
2866                                                                         dbesc($item['parent-uri']),
2867                                                                         intval($importer['importer_uid'])
2868                                                         );
2869                                                         if(count($r)) {
2870                                                                 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2871                                                                         intval($r[0]['id'])
2872                                                                 );
2873                                                         }
2874                                                 }
2875                                                 // if this is a relayed delete, propagate it to other recipients
2876
2877                                                 if($is_a_remote_delete)
2878                                                         proc_run('php',"include/notifier.php","drop",$item['id']);
2879                                         }
2880                                 }
2881                         }
2882                 }
2883         }
2884
2885
2886         foreach($feed->get_items() as $item) {
2887
2888                 $is_reply = false;              
2889                 $item_id = $item->get_id();
2890                 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
2891                 if(isset($rawthread[0]['attribs']['']['ref'])) {
2892                         $is_reply = true;
2893                         $parent_uri = $rawthread[0]['attribs']['']['ref'];
2894                 }
2895
2896                 if($is_reply) {
2897                         $community = false;
2898
2899                         if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
2900                                 $sql_extra = '';
2901                                 $community = true;
2902                                 logger('local_delivery: possible community reply');
2903                         }
2904                         else
2905                                 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
2906
2907                         // was the top-level post for this reply written by somebody on this site?
2908                         // Specifically, the recipient?
2909
2910                         $is_a_remote_comment = false;
2911                         $top_uri = $parent_uri;
2912
2913                         $r = q("select `item`.`parent-uri` from `item`
2914                                 WHERE `item`.`uri` = '%s'
2915                                 LIMIT 1",
2916                                 dbesc($parent_uri)
2917                         );
2918                         if($r && count($r)) {
2919                                 $top_uri = $r[0]['parent-uri'];
2920
2921                                 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
2922                                 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
2923                                         `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
2924                                         LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
2925                                         WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
2926                                         AND `item`.`uid` = %d
2927                                         $sql_extra
2928                                         LIMIT 1",
2929                                         dbesc($top_uri),
2930                                         dbesc($top_uri),
2931                                         dbesc($top_uri),
2932                                         intval($importer['importer_uid'])
2933                                 );
2934                                 if($r && count($r))
2935                                         $is_a_remote_comment = true;
2936                         }
2937
2938                         // Does this have the characteristics of a community or private group comment?
2939                         // If it's a reply to a wall post on a community/prvgroup page it's a 
2940                         // valid community comment. Also forum_mode makes it valid for sure. 
2941                         // If neither, it's not.
2942
2943                         if($is_a_remote_comment && $community) {
2944                                 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
2945                                         $is_a_remote_comment = false;
2946                                         logger('local_delivery: not a community reply');
2947                                 }
2948                         }
2949
2950                         if($is_a_remote_comment) {
2951                                 logger('local_delivery: received remote comment');
2952                                 $is_like = false;
2953                                 // remote reply to our post. Import and then notify everybody else.
2954
2955                                 $datarray = get_atom_elements($feed,$item);
2956
2957                                 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body`  FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2958                                         dbesc($item_id),
2959                                         intval($importer['importer_uid'])
2960                                 );
2961
2962                                 // Update content if 'updated' changes
2963
2964                                 if(count($r)) {
2965                                         $iid = $r[0]['id'];
2966                                         if (edited_timestamp_is_newer($r[0], $datarray)) {
2967
2968                                                 // do not accept (ignore) an earlier edit than one we currently have.
2969                                                 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2970                                                         continue;
2971
2972                                                 logger('received updated comment' , LOGGER_DEBUG);
2973                                                 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2974                                                         dbesc($datarray['title']),
2975                                                         dbesc($datarray['body']),
2976                                                         dbesc($datarray['tag']),
2977                                                         dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2978                                                         dbesc($item_id),
2979                                                         intval($importer['importer_uid'])
2980                                                 );
2981                                                 create_tags_from_itemuri($item_id, $importer['importer_uid']);
2982
2983                                                 proc_run('php',"include/notifier.php","comment-import",$iid);
2984
2985                                         }
2986
2987                                         continue;
2988                                 }
2989
2990
2991
2992                                 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
2993                                         intval($importer['importer_uid'])
2994                                 );
2995
2996
2997                                 $datarray['type'] = 'remote-comment';
2998                                 $datarray['wall'] = 1;
2999                                 $datarray['parent-uri'] = $parent_uri;
3000                                 $datarray['uid'] = $importer['importer_uid'];
3001                                 $datarray['owner-name'] = $own[0]['name'];
3002                                 $datarray['owner-link'] = $own[0]['url'];
3003                                 $datarray['owner-avatar'] = $own[0]['thumb'];
3004                                 $datarray['contact-id'] = $importer['id'];
3005
3006                                 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3007                                         $is_like = true;
3008                                         $datarray['type'] = 'activity';
3009                                         $datarray['gravity'] = GRAVITY_LIKE;
3010                                         $datarray['last-child'] = 0;
3011                                         // only one like or dislike per person
3012                                         $r = q("select id from item where uid = %d and `contact-id` = %d and verb = '%s' and (`thr-parent` = '%s' or `parent-uri` = '%s') and deleted = 0 limit 1",
3013                                                 intval($datarray['uid']),
3014                                                 intval($datarray['contact-id']),
3015                                                 dbesc($datarray['verb']),
3016                                                 dbesc($datarray['parent-uri']),
3017                                                 dbesc($datarray['parent-uri'])
3018
3019                                         );
3020                                         if($r && count($r))
3021                                                 continue;
3022                                 }
3023
3024                                 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3025
3026                                         $xo = parse_xml_string($datarray['object'],false);
3027                                         $xt = parse_xml_string($datarray['target'],false);
3028
3029                                         if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3030
3031                                                 // fetch the parent item
3032
3033                                                 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3034                                                         dbesc($xt->id),
3035                                                         intval($importer['importer_uid'])
3036                                                 );
3037                                                 if(! count($tagp))
3038                                                         continue;
3039
3040                                                 // extract tag, if not duplicate, and this user allows tags, add to parent item
3041
3042                                                 if($xo->id && $xo->content) {
3043                                                         $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3044                                                         if(! (stristr($tagp[0]['tag'],$newtag))) {
3045                                                                 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3046                                                                         intval($importer['importer_uid'])
3047                                                                 );
3048                                                                 if(count($i) && ! intval($i[0]['blocktags'])) {
3049                                                                         q("UPDATE item SET tag = '%s', `edited` = '%s' WHERE id = %d",
3050                                                                                 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3051                                                                                 intval($tagp[0]['id']),
3052                                                                                 dbesc(datetime_convert())
3053                                                                         );
3054                                                                         create_tags_from_item($tagp[0]['id']);
3055                                                                 }
3056                                                         }
3057                                                 }
3058                                         }
3059                                 }
3060
3061
3062                                 $posted_id = item_store($datarray);
3063                                 $parent = 0;
3064
3065                                 if($posted_id) {
3066                                         $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3067                                                 intval($posted_id),
3068                                                 intval($importer['importer_uid'])
3069                                         );
3070                                         if(count($r)) {
3071                                                 $parent = $r[0]['parent'];
3072                                                 $parent_uri = $r[0]['parent-uri'];
3073                                         }
3074
3075                                         if(! $is_like) {
3076                                                 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3077                                                         dbesc(datetime_convert()),
3078                                                         intval($importer['importer_uid']),
3079                                                         intval($r[0]['parent'])
3080                                                 );
3081
3082                                                 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3083                                                         dbesc(datetime_convert()),
3084                                                         intval($importer['importer_uid']),
3085                                                         intval($posted_id)
3086                                                 );
3087                                         }
3088
3089                                         if($posted_id && $parent) {
3090
3091                                                 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3092
3093                                                 if((! $is_like) && (! $importer['self'])) {
3094
3095                                                         require_once('include/enotify.php');
3096
3097                                                         notification(array(
3098                                                                 'type'         => NOTIFY_COMMENT,
3099                                                                 'notify_flags' => $importer['notify-flags'],
3100                                                                 'language'     => $importer['language'],
3101                                                                 'to_name'      => $importer['username'],
3102                                                                 'to_email'     => $importer['email'],
3103                                                                 'uid'          => $importer['importer_uid'],
3104                                                                 'item'         => $datarray,
3105                                                                 'link'             => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3106                                                                 'source_name'  => stripslashes($datarray['author-name']),
3107                                                                 'source_link'  => $datarray['author-link'],
3108                                                                 'source_photo' => ((link_compare($datarray['author-link'],$importer['url'])) 
3109                                                                         ? $importer['thumb'] : $datarray['author-avatar']),
3110                                                                 'verb'         => ACTIVITY_POST,
3111                                                                 'otype'        => 'item',
3112                                                                 'parent'       => $parent,
3113                                                                 'parent_uri'   => $parent_uri,
3114                                                         ));
3115
3116                                                 }
3117                                         }
3118
3119                                         return 0;
3120                                         // NOTREACHED
3121                                 }
3122                         }
3123                         else {
3124
3125                                 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3126
3127                                 $item_id  = $item->get_id();
3128                                 $datarray = get_atom_elements($feed,$item);
3129
3130                                 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3131                                         continue;
3132
3133                                 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3134                                         dbesc($item_id),
3135                                         intval($importer['importer_uid'])
3136                                 );
3137
3138                                 // Update content if 'updated' changes
3139
3140                                 if(count($r)) {
3141                                         if (edited_timestamp_is_newer($r[0], $datarray)) {
3142
3143                                                 // do not accept (ignore) an earlier edit than one we currently have.
3144                                                 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3145                                                         continue;
3146
3147                                                 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3148                                                         dbesc($datarray['title']),
3149                                                         dbesc($datarray['body']),
3150                                                         dbesc($datarray['tag']),
3151                                                         dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3152                                                         dbesc($item_id),
3153                                                         intval($importer['importer_uid'])
3154                                                 );
3155                                                 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3156                                         }
3157
3158                                         // update last-child if it changes
3159
3160                                         $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3161                                         if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3162                                                 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3163                                                         dbesc(datetime_convert()),
3164                                                         dbesc($parent_uri),
3165                                                         intval($importer['importer_uid'])
3166                                                 );
3167                                                 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s'  WHERE `uri` = '%s' AND `uid` = %d",
3168                                                         intval($allow[0]['data']),
3169                                                         dbesc(datetime_convert()),
3170                                                         dbesc($item_id),
3171                                                         intval($importer['importer_uid'])
3172                                                 );
3173                                         }
3174                                         continue;
3175                                 }
3176
3177                                 $datarray['parent-uri'] = $parent_uri;
3178                                 $datarray['uid'] = $importer['importer_uid'];
3179                                 $datarray['contact-id'] = $importer['id'];
3180                                 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3181                                         $datarray['type'] = 'activity';
3182                                         $datarray['gravity'] = GRAVITY_LIKE;
3183                                         // only one like or dislike per person
3184                                         $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`parent-uri` = '%s' OR `thr-parent` = '%s') limit 1",
3185                                                 intval($datarray['uid']),
3186                                                 intval($datarray['contact-id']),
3187                                                 dbesc($datarray['verb']),
3188                                                 dbesc($parent_uri),
3189                                                 dbesc($parent_uri)
3190                                         );
3191                                         if($r && count($r))
3192                                                 continue;
3193
3194                                 }
3195
3196                                 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3197
3198                                         $xo = parse_xml_string($datarray['object'],false);
3199                                         $xt = parse_xml_string($datarray['target'],false);
3200
3201                                         if($xt->type == ACTIVITY_OBJ_NOTE) {
3202                                                 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3203                                                         dbesc($xt->id),
3204                                                         intval($importer['importer_uid'])
3205                                                 );
3206                                                 if(! count($r))
3207                                                         continue;
3208
3209                                                 // extract tag, if not duplicate, add to parent item
3210                                                 if($xo->content) {
3211                                                         if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3212                                                                 q("UPDATE item SET tag = '%s' WHERE id = %d",
3213                                                                         dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3214                                                                         intval($r[0]['id'])
3215                                                                 );
3216                                                                 create_tags_from_item($r[0]['id']);
3217                                                         }
3218                                                 }
3219                                         }
3220                                 }
3221
3222                                 $posted_id = item_store($datarray);
3223
3224                                 // find out if our user is involved in this conversation and wants to be notified.
3225
3226                                 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3227
3228                                         $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3229                                                 dbesc($top_uri),
3230                                                 intval($importer['importer_uid'])
3231                                         );
3232
3233                                         if(count($myconv)) {
3234                                                 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3235
3236                                                 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3237                                                 if(! link_compare($datarray['author-link'],$importer_url)) {
3238
3239
3240                                                         foreach($myconv as $conv) {
3241
3242                                                                 // now if we find a match, it means we're in this conversation
3243
3244                                                                 if(! link_compare($conv['author-link'],$importer_url))
3245                                                                         continue;
3246
3247                                                                 require_once('include/enotify.php');
3248                                                                 
3249                                                                 $conv_parent = $conv['parent'];
3250
3251                                                                 notification(array(
3252                                                                         'type'         => NOTIFY_COMMENT,
3253                                                                         'notify_flags' => $importer['notify-flags'],
3254                                                                         'language'     => $importer['language'],
3255                                                                         'to_name'      => $importer['username'],
3256                                                                         'to_email'     => $importer['email'],
3257                                                                         'uid'          => $importer['importer_uid'],
3258                                                                         'item'         => $datarray,
3259                                                                         'link'             => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3260                                                                         'source_name'  => stripslashes($datarray['author-name']),
3261                                                                         'source_link'  => $datarray['author-link'],
3262                                                                         'source_photo' => ((link_compare($datarray['author-link'],$importer['url'])) 
3263                                                                                 ? $importer['thumb'] : $datarray['author-avatar']),
3264                                                                         'verb'         => ACTIVITY_POST,
3265                                                                         'otype'        => 'item',
3266                                                                         'parent'       => $conv_parent,
3267                                                                         'parent_uri'   => $parent_uri
3268
3269                                                                 ));
3270
3271                                                                 // only send one notification
3272                                                                 break;
3273                                                         }
3274                                                 }
3275                                         }
3276                                 }
3277                                 continue;
3278                         }
3279                 }
3280
3281                 else {
3282
3283                         // Head post of a conversation. Have we seen it? If not, import it.
3284
3285
3286                         $item_id  = $item->get_id();
3287                         $datarray = get_atom_elements($feed,$item);
3288
3289                         if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3290                                 $ev = bbtoevent($datarray['body']);
3291                                 if(x($ev,'desc') && x($ev,'start')) {
3292                                         $ev['cid'] = $importer['id'];
3293                                         $ev['uid'] = $importer['uid'];
3294                                         $ev['uri'] = $item_id;
3295                                         $ev['edited'] = $datarray['edited'];
3296                                         $ev['private'] = $datarray['private'];
3297
3298                                         $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3299                                                 dbesc($item_id),
3300                                                 intval($importer['uid'])
3301                                         );
3302                                         if(count($r))
3303                                                 $ev['id'] = $r[0]['id'];
3304                                         $xyz = event_store($ev);
3305                                         continue;
3306                                 }
3307                         }
3308
3309                         $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3310                                 dbesc($item_id),
3311                                 intval($importer['importer_uid'])
3312                         );
3313
3314                         // Update content if 'updated' changes
3315
3316                         if(count($r)) {
3317                                 if (edited_timestamp_is_newer($r[0], $datarray)) {
3318
3319                                         // do not accept (ignore) an earlier edit than one we currently have.
3320                                         if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3321                                                 continue;
3322
3323                                         $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3324                                                 dbesc($datarray['title']),
3325                                                 dbesc($datarray['body']),
3326                                                 dbesc($datarray['tag']),
3327                                                 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3328                                                 dbesc($item_id),
3329                                                 intval($importer['importer_uid'])
3330                                         );
3331                                         create_tags_from_itemuri($item_id, $importer['importer_uid']);
3332                                 }
3333
3334                                 // update last-child if it changes
3335
3336                                 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3337                                 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3338                                         $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3339                                                 intval($allow[0]['data']),
3340                                                 dbesc(datetime_convert()),
3341                                                 dbesc($item_id),
3342                                                 intval($importer['importer_uid'])
3343                                         );
3344                                 }
3345                                 continue;
3346                         }
3347
3348                         // This is my contact on another system, but it's really me.
3349                         // Turn this into a wall post.
3350
3351                         if($importer['remote_self'])
3352                                 $datarray['wall'] = 1;
3353
3354                         $datarray['parent-uri'] = $item_id;
3355                         $datarray['uid'] = $importer['importer_uid'];
3356                         $datarray['contact-id'] = $importer['id'];
3357
3358
3359                         if(! link_compare($datarray['owner-link'],$importer['url'])) {
3360                                 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery, 
3361                                 // but otherwise there's a possible data mixup on the sender's system.
3362                                 // the tgroup delivery code called from item_store will correct it if it's a forum,
3363                                 // but we're going to unconditionally correct it here so that the post will always be owned by our contact. 
3364                                 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3365                                 $datarray['owner-name']   = $importer['senderName'];
3366                                 $datarray['owner-link']   = $importer['url'];
3367                                 $datarray['owner-avatar'] = $importer['thumb'];
3368                         }
3369
3370                         if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3371                                 continue;
3372
3373                         $posted_id = item_store($datarray);
3374
3375                         if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3376                                 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3377                                 if(! $verb)
3378                                         continue;
3379                                 $xo = parse_xml_string($datarray['object'],false);
3380
3381                                 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3382
3383                                         // somebody was poked/prodded. Was it me?
3384
3385                                         $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3386
3387                                 foreach($links->link as $l) {
3388                                 $atts = $l->attributes();
3389                                 switch($atts['rel']) {
3390                                         case "alternate": 
3391                                                                 $Blink = $atts['href'];
3392                                                                 break;
3393                                                         default:
3394                                                                 break;
3395                                     }
3396                                 }
3397                                         if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3398
3399                                                 // send a notification
3400                                                 require_once('include/enotify.php');
3401                                                                 
3402                                                 notification(array(
3403                                                         'type'         => NOTIFY_POKE,
3404                                                         'notify_flags' => $importer['notify-flags'],
3405                                                         'language'     => $importer['language'],
3406                                                         'to_name'      => $importer['username'],
3407                                                         'to_email'     => $importer['email'],
3408                                                         'uid'          => $importer['importer_uid'],
3409                                                         'item'         => $datarray,
3410                                                         'link'             => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id,
3411                                                         'source_name'  => stripslashes($datarray['author-name']),
3412                                                         'source_link'  => $datarray['author-link'],
3413                                                         'source_photo' => ((link_compare($datarray['author-link'],$importer['url'])) 
3414                                                                 ? $importer['thumb'] : $datarray['author-avatar']),
3415                                                         'verb'         => $datarray['verb'],
3416                                                         'otype'        => 'person',
3417                                                         'activity'     => $verb,
3418
3419                                                 ));
3420                                         }
3421                                 }
3422                         }                       
3423
3424                         continue;
3425                 }
3426         }
3427
3428         return 0;
3429         // NOTREACHED
3430
3431 }
3432
3433
3434 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3435         $url = notags(trim($datarray['author-link']));
3436         $name = notags(trim($datarray['author-name']));
3437         $photo = notags(trim($datarray['author-avatar']));
3438
3439         $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3440         if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3441                 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3442
3443         if(is_array($contact)) {
3444                 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3445                         || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3446                         $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3447                                 intval(CONTACT_IS_FRIEND),
3448                                 intval($contact['id']),
3449                                 intval($importer['uid'])
3450                         );
3451                 }
3452                 // send email notification to owner?
3453         }
3454         else {
3455
3456                 // create contact record
3457
3458                 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`, 
3459                         `blocked`, `readonly`, `pending`, `writable` )
3460                         VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3461                         intval($importer['uid']),
3462                         dbesc(datetime_convert()),
3463                         dbesc($url),
3464                         dbesc(normalise_link($url)),
3465                         dbesc($name),
3466                         dbesc($nick),
3467                         dbesc($photo),
3468                         dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3469                         intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3470                 );
3471                 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3472                                 intval($importer['uid']),
3473                                 dbesc($url)
3474                 );
3475                 if(count($r))
3476                                 $contact_record = $r[0];
3477
3478                 // create notification
3479                 $hash = random_string();
3480
3481                 if(is_array($contact_record)) {
3482                         $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3483                                 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3484                                 intval($importer['uid']),
3485                                 intval($contact_record['id']),
3486                                 dbesc($hash),
3487                                 dbesc(datetime_convert())
3488                         );
3489                 }
3490                 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3491                         intval($importer['uid'])
3492                 );
3493                 $a = get_app();
3494                 if(count($r)) {
3495
3496                         if(intval($r[0]['def_gid'])) {
3497                                 require_once('include/group.php');
3498                                 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3499                         }
3500
3501                         if(($r[0]['notify-flags'] & NOTIFY_INTRO) && ($r[0]['page-flags'] == PAGE_NORMAL)) {
3502                                 $email_tpl = get_intltext_template('follow_notify_eml.tpl');
3503                                 $email = replace_macros($email_tpl, array(
3504                                         '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')),
3505                                         '$url' => $url,
3506                                         '$myname' => $r[0]['username'],
3507                                         '$siteurl' => $a->get_baseurl(),
3508                                         '$sitename' => $a->config['sitename']
3509                                 ));
3510                                 $res = mail($r[0]['email'], 
3511                                         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'),
3512                                         $email,
3513                                         'From: ' . 'Administrator' . '@' . $_SERVER['SERVER_NAME'] . "\n"
3514                                         . 'Content-type: text/plain; charset=UTF-8' . "\n"
3515                                         . 'Content-transfer-encoding: 8bit' );
3516
3517                         }
3518                 }
3519         }
3520 }
3521
3522 function lose_follower($importer,$contact,$datarray,$item) {
3523
3524         if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3525                 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3526                         intval(CONTACT_IS_SHARING),
3527                         intval($contact['id'])
3528                 );
3529         }
3530         else {
3531                 contact_remove($contact['id']);
3532         }
3533 }
3534
3535 function lose_sharer($importer,$contact,$datarray,$item) {
3536
3537         if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3538                 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3539                         intval(CONTACT_IS_FOLLOWER),
3540                         intval($contact['id'])
3541                 );
3542         }
3543         else {
3544                 contact_remove($contact['id']);
3545         }
3546 }
3547
3548
3549 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3550
3551         $a = get_app();
3552
3553         if(is_array($importer)) {
3554                 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3555                         intval($importer['uid'])
3556                 );
3557         }
3558
3559         // Diaspora has different message-ids in feeds than they do 
3560         // through the direct Diaspora protocol. If we try and use
3561         // the feed, we'll get duplicates. So don't.
3562
3563         if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3564                 return;
3565
3566         $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3567
3568         // Use a single verify token, even if multiple hubs
3569
3570         $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3571
3572         $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3573
3574         logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: '  . $push_url . ' with verifier ' . $verify_token);
3575
3576         if(! strlen($contact['hub-verify'])) {
3577                 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3578                         dbesc($verify_token),
3579                         intval($contact['id'])
3580                 );
3581         }
3582
3583         post_url($url,$params);
3584
3585         logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3586
3587         return;
3588
3589 }
3590
3591
3592 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3593         $o = '';
3594         if(! $tag)
3595                 return $o;
3596         $name = xmlify($name);
3597         $uri = xmlify($uri);
3598         $h = intval($h);
3599         $w = intval($w);
3600         $photo = xmlify($photo);
3601
3602
3603         $o .= "<$tag>\r\n";
3604         $o .= "<name>$name</name>\r\n";
3605         $o .= "<uri>$uri</uri>\r\n";
3606         $o .= '<link rel="photo"  type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3607         $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3608
3609         call_hooks('atom_author', $o);
3610
3611         $o .= "</$tag>\r\n";
3612         return $o;
3613 }
3614
3615 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3616
3617         $a = get_app();
3618
3619         if(! $item['parent'])
3620                 return;
3621
3622         if($item['deleted'])
3623                 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3624
3625
3626         if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3627                 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3628         else
3629                 $body = $item['body'];
3630
3631         $o = "\r\n\r\n<entry>\r\n";
3632
3633         if(is_array($author))
3634                 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3635         else
3636                 $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']));
3637         if(strlen($item['owner-name']))
3638                 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3639
3640         if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3641                 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3642                 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' .  xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3643         }
3644
3645         $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3646         $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3647         $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3648         $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
3649         $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
3650         $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
3651         $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
3652         if($comment)
3653                 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
3654
3655         if($item['location']) {
3656                 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
3657                 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
3658         }
3659
3660         if($item['coord'])
3661                 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
3662
3663         if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
3664                 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
3665
3666         if($item['extid'])
3667                 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
3668         if($item['bookmark'])
3669                 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
3670
3671         if($item['app'])
3672                 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
3673
3674         if($item['guid'])
3675                 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
3676
3677         if($item['signed_text']) {
3678                 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
3679                 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
3680         }
3681
3682         $verb = construct_verb($item);
3683         $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
3684         $actobj = construct_activity_object($item);
3685         if(strlen($actobj))
3686                 $o .= $actobj;
3687         $actarg = construct_activity_target($item);
3688         if(strlen($actarg))
3689                 $o .= $actarg;
3690
3691         $tags = item_getfeedtags($item);
3692         if(count($tags)) {
3693                 foreach($tags as $t) {
3694                         $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
3695                 }
3696         }
3697
3698         $o .= item_getfeedattach($item);
3699
3700         $mentioned = get_mentions($item);
3701         if($mentioned)
3702                 $o .= $mentioned;
3703         
3704         call_hooks('atom_entry', $o);
3705
3706         $o .= '</entry>' . "\r\n";
3707         
3708         return $o;
3709 }
3710
3711 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
3712
3713         if(get_config('system','disable_embedded'))
3714                 return $s;
3715
3716         $a = get_app();
3717
3718         logger('fix_private_photos: check for photos', LOGGER_DEBUG);
3719         $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
3720
3721         $orig_body = $s;
3722         $new_body = '';
3723
3724         $img_start = strpos($orig_body, '[img');
3725         $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3726         $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3727         while( ($img_st_close !== false) && ($img_len !== false) ) {
3728
3729                 $img_st_close++; // make it point to AFTER the closing bracket
3730                 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
3731
3732                 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
3733
3734
3735                 if(stristr($image , $site . '/photo/')) {
3736                         // Only embed locally hosted photos
3737                         $replace = false;
3738                         $i = basename($image);
3739                         $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
3740                         $x = strpos($i,'-');
3741
3742                         if($x) {
3743                                 $res = substr($i,$x+1);
3744                                 $i = substr($i,0,$x);
3745                                 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
3746                                         dbesc($i),
3747                                         intval($res),
3748                                         intval($uid)
3749                                 );
3750                                 if($r) {
3751
3752                                         // Check to see if we should replace this photo link with an embedded image
3753                                         // 1. No need to do so if the photo is public
3754                                         // 2. If there's a contact-id provided, see if they're in the access list
3755                                         //    for the photo. If so, embed it. 
3756                                         // 3. Otherwise, if we have an item, see if the item permissions match the photo
3757                                         //    permissions, regardless of order but first check to see if they're an exact
3758                                         //    match to save some processing overhead.
3759
3760                                         if(has_permissions($r[0])) {
3761                                                 if($cid) {
3762                                                         $recips = enumerate_permissions($r[0]);
3763                                                         if(in_array($cid, $recips)) {
3764                                                                 $replace = true;        
3765                                                         }
3766                                                 }
3767                                                 elseif($item) {
3768                                                         if(compare_permissions($item,$r[0]))
3769                                                                 $replace = true;
3770                                                 }
3771                                         }
3772                                         if($replace) {
3773                                                 $data = $r[0]['data'];
3774                                                 $type = $r[0]['type'];
3775
3776                                                 // If a custom width and height were specified, apply before embedding
3777                                                 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
3778                                                         logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
3779
3780                                                         $width = intval($match[1]);
3781                                                         $height = intval($match[2]);
3782
3783                                                         $ph = new Photo($data, $type);
3784                                                         if($ph->is_valid()) {
3785                                                                 $ph->scaleImage(max($width, $height));
3786                                                                 $data = $ph->imageString();
3787                                                                 $type = $ph->getType();
3788                                                         }
3789                                                 }
3790
3791                                                 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
3792                                                 $image = 'data:' . $type . ';base64,' . base64_encode($data);
3793                                                 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
3794                                         }
3795                                 }
3796                         }
3797                 }       
3798
3799                 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
3800                 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
3801                 if($orig_body === false)
3802                         $orig_body = '';
3803
3804                 $img_start = strpos($orig_body, '[img');
3805                 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
3806                 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
3807         }
3808
3809         $new_body = $new_body . $orig_body;
3810
3811         return($new_body);
3812 }
3813
3814
3815 function has_permissions($obj) {
3816         if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
3817                 return true;
3818         return false;
3819 }
3820
3821 function compare_permissions($obj1,$obj2) {
3822         // first part is easy. Check that these are exactly the same. 
3823         if(($obj1['allow_cid'] == $obj2['allow_cid'])
3824                 && ($obj1['allow_gid'] == $obj2['allow_gid'])
3825                 && ($obj1['deny_cid'] == $obj2['deny_cid'])
3826                 && ($obj1['deny_gid'] == $obj2['deny_gid']))
3827                 return true;
3828
3829         // This is harder. Parse all the permissions and compare the resulting set.
3830
3831         $recipients1 = enumerate_permissions($obj1);
3832         $recipients2 = enumerate_permissions($obj2);
3833         sort($recipients1);
3834         sort($recipients2);
3835         if($recipients1 == $recipients2)
3836                 return true;
3837         return false;
3838 }
3839
3840 // returns an array of contact-ids that are allowed to see this object
3841
3842 function enumerate_permissions($obj) {
3843         require_once('include/group.php');
3844         $allow_people = expand_acl($obj['allow_cid']);
3845         $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
3846         $deny_people  = expand_acl($obj['deny_cid']);
3847         $deny_groups  = expand_groups(expand_acl($obj['deny_gid']));
3848         $recipients   = array_unique(array_merge($allow_people,$allow_groups));
3849         $deny         = array_unique(array_merge($deny_people,$deny_groups));
3850         $recipients   = array_diff($recipients,$deny);
3851         return $recipients;
3852 }
3853
3854 function item_getfeedtags($item) {
3855         $ret = array();
3856         $matches = false;
3857         $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3858         if($cnt) {
3859                 for($x = 0; $x < $cnt; $x ++) {
3860                         if($matches[1][$x])
3861                                 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
3862                 }
3863         }
3864         $matches = false; 
3865         $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
3866         if($cnt) {
3867                 for($x = 0; $x < $cnt; $x ++) {
3868                         if($matches[1][$x])
3869                                 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
3870                 }
3871         } 
3872         return $ret;
3873 }
3874
3875 function item_getfeedattach($item) {
3876         $ret = '';
3877         $arr = explode('[/attach],',$item['attach']);
3878         if(count($arr)) {
3879                 foreach($arr as $r) {
3880                         $matches = false;
3881                         $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
3882                         if($cnt) {
3883                                 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
3884                                 if(intval($matches[2]))
3885                                         $ret .= 'length="' . intval($matches[2]) . '" ';
3886                                 if($matches[4] !== ' ')
3887                                         $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
3888                                 $ret .= ' />' . "\r\n";
3889                         }
3890                 }
3891         }
3892         return $ret;
3893 }
3894
3895
3896         
3897 function item_expire($uid,$days) {
3898
3899         if((! $uid) || ($days < 1))
3900                 return;
3901
3902         // $expire_network_only = save your own wall posts
3903         // and just expire conversations started by others
3904
3905         $expire_network_only = get_pconfig($uid,'expire','network_only');
3906         $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
3907
3908         $r = q("SELECT * FROM `item` 
3909                 WHERE `uid` = %d 
3910                 AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY 
3911                 AND `id` = `parent` 
3912                 $sql_extra
3913                 AND `deleted` = 0",
3914                 intval($uid),
3915                 intval($days)
3916         );
3917
3918         if(! count($r))
3919                 return;
3920
3921         $expire_items = get_pconfig($uid, 'expire','items');
3922         $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
3923
3924         $expire_notes = get_pconfig($uid, 'expire','notes');
3925         $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
3926
3927         $expire_starred = get_pconfig($uid, 'expire','starred');
3928         $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
3929
3930         $expire_photos = get_pconfig($uid, 'expire','photos');
3931         $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
3932
3933         logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
3934
3935         foreach($r as $item) {
3936
3937                 // don't expire filed items
3938
3939                 if(strpos($item['file'],'[') !== false)
3940                         continue;
3941
3942                 // Only expire posts, not photos and photo comments
3943
3944                 if($expire_photos==0 && strlen($item['resource-id']))
3945                         continue;
3946                 if($expire_starred==0 && intval($item['starred']))
3947                         continue;
3948                 if($expire_notes==0 && $item['type']=='note')
3949                         continue;
3950                 if($expire_items==0 && $item['type']!='note')
3951                         continue;
3952
3953                 drop_item($item['id'],false);
3954         }
3955
3956         proc_run('php',"include/notifier.php","expire","$uid");
3957         
3958 }
3959
3960
3961 function drop_items($items) {
3962         $uid = 0;
3963
3964         if(! local_user() && ! remote_user())
3965                 return;
3966
3967         if(count($items)) {
3968                 foreach($items as $item) {
3969                         $owner = drop_item($item,false);
3970                         if($owner && ! $uid)
3971                                 $uid = $owner;
3972                 }
3973         }
3974
3975         // multiple threads may have been deleted, send an expire notification
3976
3977         if($uid)
3978                 proc_run('php',"include/notifier.php","expire","$uid");
3979 }
3980
3981
3982 function drop_item($id,$interactive = true) {
3983
3984         $a = get_app();
3985
3986         // locate item to be deleted
3987
3988         $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
3989                 intval($id)
3990         );
3991
3992         if(! count($r)) {
3993                 if(! $interactive)
3994                         return 0;
3995                 notice( t('Item not found.') . EOL);
3996                 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
3997         }
3998
3999         $item = $r[0];
4000
4001         $owner = $item['uid'];
4002
4003         $cid = 0;
4004
4005         // check if logged in user is either the author or owner of this item
4006
4007         if(is_array($_SESSION['remote'])) {
4008                 foreach($_SESSION['remote'] as $visitor) {
4009                         if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4010                                 $cid = $visitor['cid'];
4011                                 break;
4012                         }
4013                 }
4014         }
4015
4016
4017         if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4018
4019                 // Check if we should do HTML-based delete confirmation
4020                 if($_REQUEST['confirm']) {
4021                         // <form> can't take arguments in its "action" parameter
4022                         // so add any arguments as hidden inputs
4023                         $query = explode_querystring($a->query_string);
4024                         $inputs = array();
4025                         foreach($query['args'] as $arg) {
4026                                 if(strpos($arg, 'confirm=') === false) {
4027                                         $arg_parts = explode('=', $arg);
4028                                         $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4029                                 }
4030                         }
4031
4032                         return replace_macros(get_markup_template('confirm.tpl'), array(
4033                                 '$method' => 'get',
4034                                 '$message' => t('Do you really want to delete this item?'),
4035                                 '$extra_inputs' => $inputs,
4036                                 '$confirm' => t('Yes'),
4037                                 '$confirm_url' => $query['base'],
4038                                 '$confirm_name' => 'confirmed',
4039                                 '$cancel' => t('Cancel'),
4040                         ));
4041                 }
4042                 // Now check how the user responded to the confirmation query
4043                 if($_REQUEST['canceled']) {
4044                         goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4045                 }
4046
4047                 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4048                 // delete the item
4049
4050                 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4051                         dbesc(datetime_convert()),
4052                         dbesc(datetime_convert()),
4053                         intval($item['id'])
4054                 );
4055                 create_tags_from_item($item['id']);
4056
4057                 // clean up categories and tags so they don't end up as orphans
4058
4059                 $matches = false;
4060                 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4061                 if($cnt) {
4062                         foreach($matches as $mtch) {
4063                                 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4064                         }
4065                 }
4066
4067                 $matches = false;
4068
4069                 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4070                 if($cnt) {
4071                         foreach($matches as $mtch) {
4072                                 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4073                         }
4074                 }
4075
4076                 // If item is a link to a photo resource, nuke all the associated photos 
4077                 // (visitors will not have photo resources)
4078                 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4079                 // generate a resource-id and therefore aren't intimately linked to the item. 
4080
4081                 if(strlen($item['resource-id'])) {
4082                         q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4083                                 dbesc($item['resource-id']),
4084                                 intval($item['uid'])
4085                         );
4086                         // ignore the result
4087                 }
4088
4089                 // If item is a link to an event, nuke the event record.
4090
4091                 if(intval($item['event-id'])) {
4092                         q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4093                                 intval($item['event-id']),
4094                                 intval($item['uid'])
4095                         );
4096                         // ignore the result
4097                 }
4098
4099                 // clean up item_id and sign meta-data tables
4100
4101                 /*
4102                 // Old code - caused very long queries and warning entries in the mysql logfiles:
4103
4104                 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4105                         intval($item['id']),
4106                         intval($item['uid'])
4107                 );
4108
4109                 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4110                         intval($item['id']),
4111                         intval($item['uid'])
4112                 );
4113                 */
4114
4115                 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4116
4117                 // Creating list of parents
4118                 $r = q("select id from item where parent = %d and uid = %d",
4119                         intval($item['id']),
4120                         intval($item['uid'])
4121                 );
4122
4123                 $parentid = "";
4124
4125                 foreach ($r AS $row) {
4126                         if ($parentid != "")
4127                                 $parentid .= ", ";
4128
4129                         $parentid .= $row["id"];
4130                 }
4131
4132                 // Now delete them
4133                 if ($parentid != "") {
4134                         $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4135
4136                         $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4137                 }
4138
4139                 // If it's the parent of a comment thread, kill all the kids
4140
4141                 if($item['uri'] == $item['parent-uri']) {
4142                         $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4143                                 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4144                                 dbesc(datetime_convert()),
4145                                 dbesc(datetime_convert()),
4146                                 dbesc($item['parent-uri']),
4147                                 intval($item['uid'])
4148                         );
4149                         create_tags_from_item($item['parent-uri'], $item['uid']);
4150                         // ignore the result
4151                 }
4152                 else {
4153                         // ensure that last-child is set in case the comment that had it just got wiped.
4154                         q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4155                                 dbesc(datetime_convert()),
4156                                 dbesc($item['parent-uri']),
4157                                 intval($item['uid'])
4158                         );
4159                         // who is the last child now?
4160                         $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",
4161                                 dbesc($item['parent-uri']),
4162                                 intval($item['uid'])
4163                         );
4164                         if(count($r)) {
4165                                 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4166                                         intval($r[0]['id'])
4167                                 );
4168                         }
4169
4170                         // Add a relayable_retraction signature for Diaspora.
4171                         store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4172                 }
4173                 $drop_id = intval($item['id']);
4174
4175                 // send the notification upstream/downstream as the case may be
4176
4177                 proc_run('php',"include/notifier.php","drop","$drop_id");
4178
4179                 if(! $interactive)
4180                         return $owner;
4181                 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4182                 //NOTREACHED
4183         }
4184         else {
4185                 if(! $interactive)
4186                         return 0;
4187                 notice( t('Permission denied.') . EOL);
4188                 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4189                 //NOTREACHED
4190         }
4191
4192 }
4193
4194
4195 function first_post_date($uid,$wall = false) {
4196         $r = q("select id, created from item 
4197                 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0 
4198                 and id = parent
4199                 order by created asc limit 1",
4200                 intval($uid),
4201                 intval($wall ? 1 : 0)
4202         );
4203         if(count($r)) {
4204 //              logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4205                 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4206         }
4207         return false;
4208 }
4209
4210 function posted_dates($uid,$wall) {
4211         $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4212
4213         $dthen = first_post_date($uid,$wall);
4214         if(! $dthen)
4215                 return array();
4216
4217         // If it's near the end of a long month, backup to the 28th so that in 
4218         // consecutive loops we'll always get a whole month difference.
4219
4220         if(intval(substr($dnow,8)) > 28)
4221                 $dnow = substr($dnow,0,8) . '28';
4222         if(intval(substr($dthen,8)) > 28)
4223                 $dnow = substr($dthen,0,8) . '28';
4224
4225         $ret = array();
4226         // Starting with the current month, get the first and last days of every
4227         // month down to and including the month of the first post
4228         while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4229                 $dstart = substr($dnow,0,8) . '01';
4230                 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4231                 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4232                 $end_month = datetime_convert('','',$dend,'Y-m-d');
4233                 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4234                 $ret[] = array($str,$end_month,$start_month);
4235                 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4236         }
4237         return $ret;
4238 }
4239
4240
4241 function posted_date_widget($url,$uid,$wall) {
4242         $o = '';
4243
4244         if(! feature_enabled($uid,'archives'))
4245                 return $o;
4246
4247         // For former Facebook folks that left because of "timeline"
4248
4249 /*      if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4250                 return $o;*/
4251
4252         $ret = posted_dates($uid,$wall);
4253         if(! count($ret))
4254                 return $o;
4255
4256         $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4257                 '$title' => t('Archives'),
4258                 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4259                 '$url' => $url,
4260                 '$dates' => $ret
4261         ));
4262         return $o;
4263 }
4264
4265 function store_diaspora_retract_sig($item, $user, $baseurl) {
4266         // Note that we can't add a target_author_signature
4267         // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4268         // the comment, that means we're the home of the post, and Diaspora will only
4269         // check the parent_author_signature of retractions that it doesn't have to relay further
4270         //
4271         // I don't think this function gets called for an "unlike," but I'll check anyway
4272
4273         $enabled = intval(get_config('system','diaspora_enabled'));
4274         if(! $enabled) {
4275                 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4276                 return;
4277         }
4278
4279         logger('drop_item: storing diaspora retraction signature');
4280
4281         $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4282
4283         if(local_user() == $item['uid']) {
4284
4285                 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4286                 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4287         }
4288         else {
4289                 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4290                         $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4291                 );
4292                 if(count($r)) {
4293                         // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4294                         // only handles DFRN deletes
4295                         $handle_baseurl_start = strpos($r['url'],'://') + 3;
4296                         $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4297                         $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4298                         $authorsig = '';
4299                 }
4300         }
4301
4302         if(isset($handle))
4303                 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4304                         intval($item['id']),
4305                         dbesc($signed_text),
4306                         dbesc($authorsig),
4307                         dbesc($handle)
4308                 );
4309
4310         return;
4311 }