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