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