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