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