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