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