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