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