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