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