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