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