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