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