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