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