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