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