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