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