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