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