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