]> git.mxchange.org Git - friendica.git/blob - include/items.php
API: Set the application name to "api" (if not defined), "remote_self" has an applica...
[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(urlencode($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         require_once('include/contact_selectors.php');
1942
1943         if(! strlen($xml)) {
1944                 logger('consume_feed: empty input');
1945                 return;
1946         }
1947
1948         $feed = new SimplePie();
1949         $feed->set_raw_data($xml);
1950         if($datedir)
1951                 $feed->enable_order_by_date(true);
1952         else
1953                 $feed->enable_order_by_date(false);
1954         $feed->init();
1955
1956         if($feed->error())
1957                 logger('consume_feed: Error parsing XML: ' . $feed->error());
1958
1959         $permalink = $feed->get_permalink();
1960
1961         // Check at the feed level for updated contact name and/or photo
1962
1963         $name_updated  = '';
1964         $new_name = '';
1965         $photo_timestamp = '';
1966         $photo_url = '';
1967         $birthday = '';
1968
1969         $hubs = $feed->get_links('hub');
1970         logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
1971
1972         if(count($hubs))
1973                 $hub = implode(',', $hubs);
1974
1975         $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
1976         if(! $rawtags)
1977                 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
1978         if($rawtags) {
1979                 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
1980                 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
1981                         $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
1982                         $new_name = $elems['name'][0]['data'];
1983                 }
1984                 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
1985                         $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
1986                         $photo_url = $elems['link'][0]['attribs']['']['href'];
1987                 }
1988
1989                 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
1990                         $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
1991                 }
1992         }
1993
1994         if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
1995                 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
1996                 require_once("include/Photo.php");
1997                 $photo_failure = false;
1998                 $have_photo = false;
1999
2000                 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2001                         intval($contact['id']),
2002                         intval($contact['uid'])
2003                 );
2004                 if(count($r)) {
2005                         $resource_id = $r[0]['resource-id'];
2006                         $have_photo = true;
2007                 }
2008                 else {
2009                         $resource_id = photo_new_resource();
2010                 }
2011
2012                 $img_str = fetch_url($photo_url,true);
2013                 // guess mimetype from headers or filename
2014                 $type = guess_image_type($photo_url,true);
2015
2016
2017                 $img = new Photo($img_str, $type);
2018                 if($img->is_valid()) {
2019                         if($have_photo) {
2020                                 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2021                                         dbesc($resource_id),
2022                                         intval($contact['id']),
2023                                         intval($contact['uid'])
2024                                 );
2025                         }
2026
2027                         $img->scaleImageSquare(175);
2028
2029                         $hash = $resource_id;
2030                         $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2031
2032                         $img->scaleImage(80);
2033                         $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2034
2035                         $img->scaleImage(48);
2036                         $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2037
2038                         $a = get_app();
2039
2040                         q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2041                                 WHERE `uid` = %d AND `id` = %d",
2042                                 dbesc(datetime_convert()),
2043                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2044                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2045                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2046                                 intval($contact['uid']),
2047                                 intval($contact['id'])
2048                         );
2049                 }
2050         }
2051
2052         if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2053                 $r = q("select * from contact where uid = %d and id = %d limit 1",
2054                         intval($contact['uid']),
2055                         intval($contact['id'])
2056                 );
2057
2058                 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2059                         dbesc(notags(trim($new_name))),
2060                         dbesc(datetime_convert()),
2061                         intval($contact['uid']),
2062                         intval($contact['id'])
2063                 );
2064
2065                 // do our best to update the name on content items
2066
2067                 if(count($r)) {
2068                         q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2069                                 dbesc(notags(trim($new_name))),
2070                                 dbesc($r[0]['name']),
2071                                 dbesc($r[0]['url']),
2072                                 intval($contact['uid'])
2073                         );
2074                 }
2075         }
2076
2077         if(strlen($birthday)) {
2078                 if(substr($birthday,0,4) != $contact['bdyear']) {
2079                         logger('consume_feed: updating birthday: ' . $birthday);
2080
2081                         /**
2082                          *
2083                          * Add new birthday event for this person
2084                          *
2085                          * $bdtext is just a readable placeholder in case the event is shared
2086                          * with others. We will replace it during presentation to our $importer
2087                          * to contain a sparkle link and perhaps a photo.
2088                          *
2089                          */
2090
2091                         $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2092                         $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2093
2094
2095                         $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2096                                 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2097                                 intval($contact['uid']),
2098                                 intval($contact['id']),
2099                                 dbesc(datetime_convert()),
2100                                 dbesc(datetime_convert()),
2101                                 dbesc(datetime_convert('UTC','UTC', $birthday)),
2102                                 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2103                                 dbesc($bdtext),
2104                                 dbesc($bdtext2),
2105                                 dbesc('birthday')
2106                         );
2107
2108
2109                         // update bdyear
2110
2111                         q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2112                                 dbesc(substr($birthday,0,4)),
2113                                 intval($contact['uid']),
2114                                 intval($contact['id'])
2115                         );
2116
2117                         // This function is called twice without reloading the contact
2118                         // Make sure we only create one event. This is why &$contact
2119                         // is a reference var in this function
2120
2121                         $contact['bdyear'] = substr($birthday,0,4);
2122                 }
2123
2124         }
2125
2126         $community_page = 0;
2127         $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2128         if($rawtags) {
2129                 $community_page = intval($rawtags[0]['data']);
2130         }
2131         if(is_array($contact) && intval($contact['forum']) != $community_page) {
2132                 q("update contact set forum = %d where id = %d",
2133                         intval($community_page),
2134                         intval($contact['id'])
2135                 );
2136                 $contact['forum'] = (string) $community_page;
2137         }
2138
2139
2140         // process any deleted entries
2141
2142         $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2143         if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2144                 foreach($del_entries as $dentry) {
2145                         $deleted = false;
2146                         if(isset($dentry['attribs']['']['ref'])) {
2147                                 $uri = $dentry['attribs']['']['ref'];
2148                                 $deleted = true;
2149                                 if(isset($dentry['attribs']['']['when'])) {
2150                                         $when = $dentry['attribs']['']['when'];
2151                                         $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2152                                 }
2153                                 else
2154                                         $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2155                         }
2156                         if($deleted && is_array($contact)) {
2157                                 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2158                                         WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2159                                         dbesc($uri),
2160                                         intval($importer['uid']),
2161                                         intval($contact['id'])
2162                                 );
2163                                 if(count($r)) {
2164                                         $item = $r[0];
2165
2166                                         if(! $item['deleted'])
2167                                                 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2168
2169                                         if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2170                                                 $xo = parse_xml_string($item['object'],false);
2171                                                 $xt = parse_xml_string($item['target'],false);
2172                                                 if($xt->type === ACTIVITY_OBJ_NOTE) {
2173                                                         $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2174                                                                 dbesc($xt->id),
2175                                                                 intval($importer['importer_uid'])
2176                                                         );
2177                                                         if(count($i)) {
2178
2179                                                                 // For tags, the owner cannot remove the tag on the author's copy of the post.
2180
2181                                                                 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2182                                                                 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2183                                                                 $author_copy = (($item['origin']) ? true : false);
2184
2185                                                                 if($owner_remove && $author_copy)
2186                                                                         continue;
2187                                                                 if($author_remove || $owner_remove) {
2188                                                                         $tags = explode(',',$i[0]['tag']);
2189                                                                         $newtags = array();
2190                                                                         if(count($tags)) {
2191                                                                                 foreach($tags as $tag)
2192                                                                                         if(trim($tag) !== trim($xo->body))
2193                                                                                                 $newtags[] = trim($tag);
2194                                                                         }
2195                                                                         q("update item set tag = '%s' where id = %d",
2196                                                                                 dbesc(implode(',',$newtags)),
2197                                                                                 intval($i[0]['id'])
2198                                                                         );
2199                                                                         create_tags_from_item($i[0]['id']);
2200                                                                 }
2201                                                         }
2202                                                 }
2203                                         }
2204
2205                                         if($item['uri'] == $item['parent-uri']) {
2206                                                 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2207                                                         `body` = '', `title` = ''
2208                                                         WHERE `parent-uri` = '%s' AND `uid` = %d",
2209                                                         dbesc($when),
2210                                                         dbesc(datetime_convert()),
2211                                                         dbesc($item['uri']),
2212                                                         intval($importer['uid'])
2213                                                 );
2214                                                 create_tags_from_itemuri($item['uri'], $importer['uid']);
2215                                                 create_files_from_itemuri($item['uri'], $importer['uid']);
2216                                                 update_thread_uri($item['uri'], $importer['uid']);
2217                                         }
2218                                         else {
2219                                                 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2220                                                         `body` = '', `title` = ''
2221                                                         WHERE `uri` = '%s' AND `uid` = %d",
2222                                                         dbesc($when),
2223                                                         dbesc(datetime_convert()),
2224                                                         dbesc($uri),
2225                                                         intval($importer['uid'])
2226                                                 );
2227                                                 create_tags_from_itemuri($uri, $importer['uid']);
2228                                                 create_files_from_itemuri($uri, $importer['uid']);
2229                                                 if($item['last-child']) {
2230                                                         // ensure that last-child is set in case the comment that had it just got wiped.
2231                                                         q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2232                                                                 dbesc(datetime_convert()),
2233                                                                 dbesc($item['parent-uri']),
2234                                                                 intval($item['uid'])
2235                                                         );
2236                                                         // who is the last child now?
2237                                                         $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2238                                                                 ORDER BY `created` DESC LIMIT 1",
2239                                                                         dbesc($item['parent-uri']),
2240                                                                         intval($importer['uid'])
2241                                                         );
2242                                                         if(count($r)) {
2243                                                                 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2244                                                                         intval($r[0]['id'])
2245                                                                 );
2246                                                         }
2247                                                 }
2248                                         }
2249                                 }
2250                         }
2251                 }
2252         }
2253
2254         // Now process the feed
2255
2256         if($feed->get_item_quantity()) {
2257
2258                 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2259
2260         // in inverse date order
2261                 if ($datedir)
2262                         $items = array_reverse($feed->get_items());
2263                 else
2264                         $items = $feed->get_items();
2265
2266
2267                 foreach($items as $item) {
2268
2269                         $is_reply = false;
2270                         $item_id = $item->get_id();
2271                         $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2272                         if(isset($rawthread[0]['attribs']['']['ref'])) {
2273                                 $is_reply = true;
2274                                 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2275                         }
2276
2277                         if(($is_reply) && is_array($contact)) {
2278
2279                                 if($pass == 1)
2280                                         continue;
2281
2282                                 // not allowed to post
2283
2284                                 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2285                                         continue;
2286
2287
2288                                 // Have we seen it? If not, import it.
2289
2290                                 $item_id  = $item->get_id();
2291                                 $datarray = get_atom_elements($feed, $item, $contact);
2292
2293                                 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2294                                         $datarray['author-name'] = $contact['name'];
2295                                 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2296                                         $datarray['author-link'] = $contact['url'];
2297                                 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2298                                         $datarray['author-avatar'] = $contact['thumb'];
2299
2300                                 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2301                                         logger('consume_feed: no author information! ' . print_r($datarray,true));
2302                                         continue;
2303                                 }
2304
2305                                 $force_parent = false;
2306                                 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2307                                         if($contact['network'] === NETWORK_OSTATUS)
2308                                                 $force_parent = true;
2309                                         if(strlen($datarray['title']))
2310                                                 unset($datarray['title']);
2311                                         $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2312                                                 dbesc(datetime_convert()),
2313                                                 dbesc($parent_uri),
2314                                                 intval($importer['uid'])
2315                                         );
2316                                         $datarray['last-child'] = 1;
2317                                         update_thread_uri($parent_uri, $importer['uid']);
2318                                 }
2319
2320
2321                                 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2322                                         dbesc($item_id),
2323                                         intval($importer['uid'])
2324                                 );
2325
2326                                 // Update content if 'updated' changes
2327
2328                                 if(count($r)) {
2329                                         if (edited_timestamp_is_newer($r[0], $datarray)) {
2330
2331                                                 // do not accept (ignore) an earlier edit than one we currently have.
2332                                                 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2333                                                         continue;
2334
2335                                                 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2336                                                         dbesc($datarray['title']),
2337                                                         dbesc($datarray['body']),
2338                                                         dbesc($datarray['tag']),
2339                                                         dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2340                                                         dbesc(datetime_convert()),
2341                                                         dbesc($item_id),
2342                                                         intval($importer['uid'])
2343                                                 );
2344                                                 create_tags_from_itemuri($item_id, $importer['uid']);
2345                                                 update_thread_uri($item_id, $importer['uid']);
2346                                         }
2347
2348                                         // update last-child if it changes
2349
2350                                         $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2351                                         if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2352                                                 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2353                                                         dbesc(datetime_convert()),
2354                                                         dbesc($parent_uri),
2355                                                         intval($importer['uid'])
2356                                                 );
2357                                                 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s'  WHERE `uri` = '%s' AND `uid` = %d",
2358                                                         intval($allow[0]['data']),
2359                                                         dbesc(datetime_convert()),
2360                                                         dbesc($item_id),
2361                                                         intval($importer['uid'])
2362                                                 );
2363                                                 update_thread_uri($item_id, $importer['uid']);
2364                                         }
2365                                         continue;
2366                                 }
2367
2368
2369                                 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2370                                         // one way feed - no remote comment ability
2371                                         $datarray['last-child'] = 0;
2372                                 }
2373                                 $datarray['parent-uri'] = $parent_uri;
2374                                 $datarray['uid'] = $importer['uid'];
2375                                 $datarray['contact-id'] = $contact['id'];
2376                                 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2377                                         $datarray['type'] = 'activity';
2378                                         $datarray['gravity'] = GRAVITY_LIKE;
2379                                         // only one like or dislike per person
2380                                         // splitted into two queries for performance issues
2381                                         $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",
2382                                                 intval($datarray['uid']),
2383                                                 intval($datarray['contact-id']),
2384                                                 dbesc($datarray['verb']),
2385                                                 dbesc($parent_uri)
2386                                         );
2387                                         if($r && count($r))
2388                                                 continue;
2389
2390                                         $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",
2391                                                 intval($datarray['uid']),
2392                                                 intval($datarray['contact-id']),
2393                                                 dbesc($datarray['verb']),
2394                                                 dbesc($parent_uri)
2395                                         );
2396                                         if($r && count($r))
2397                                                 continue;
2398                                 }
2399
2400                                 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2401                                         $xo = parse_xml_string($datarray['object'],false);
2402                                         $xt = parse_xml_string($datarray['target'],false);
2403
2404                                         if($xt->type == ACTIVITY_OBJ_NOTE) {
2405                                                 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2406                                                         dbesc($xt->id),
2407                                                         intval($importer['importer_uid'])
2408                                                 );
2409                                                 if(! count($r))
2410                                                         continue;
2411
2412                                                 // extract tag, if not duplicate, add to parent item
2413                                                 if($xo->id && $xo->content) {
2414                                                         $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2415                                                         if(! (stristr($r[0]['tag'],$newtag))) {
2416                                                                 q("UPDATE item SET tag = '%s' WHERE id = %d",
2417                                                                         dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2418                                                                         intval($r[0]['id'])
2419                                                                 );
2420                                                                 create_tags_from_item($r[0]['id']);
2421                                                         }
2422                                                 }
2423                                         }
2424                                 }
2425
2426                                 $r = item_store($datarray,$force_parent);
2427                                 continue;
2428                         }
2429
2430                         else {
2431
2432                                 // Head post of a conversation. Have we seen it? If not, import it.
2433
2434                                 $item_id  = $item->get_id();
2435
2436                                 $datarray = get_atom_elements($feed, $item, $contact);
2437
2438                                 if(is_array($contact)) {
2439                                         if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2440                                                 $datarray['author-name'] = $contact['name'];
2441                                         if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2442                                                 $datarray['author-link'] = $contact['url'];
2443                                         if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2444                                                 $datarray['author-avatar'] = $contact['thumb'];
2445                                 }
2446
2447                                 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2448                                         logger('consume_feed: no author information! ' . print_r($datarray,true));
2449                                         continue;
2450                                 }
2451
2452                                 // special handling for events
2453
2454                                 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2455                                         $ev = bbtoevent($datarray['body']);
2456                                         if(x($ev,'desc') && x($ev,'start')) {
2457                                                 $ev['uid'] = $importer['uid'];
2458                                                 $ev['uri'] = $item_id;
2459                                                 $ev['edited'] = $datarray['edited'];
2460                                                 $ev['private'] = $datarray['private'];
2461
2462                                                 if(is_array($contact))
2463                                                         $ev['cid'] = $contact['id'];
2464                                                 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2465                                                         dbesc($item_id),
2466                                                         intval($importer['uid'])
2467                                                 );
2468                                                 if(count($r))
2469                                                         $ev['id'] = $r[0]['id'];
2470                                                 $xyz = event_store($ev);
2471                                                 continue;
2472                                         }
2473                                 }
2474
2475                                 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2476                                         if(strlen($datarray['title']))
2477                                                 unset($datarray['title']);
2478                                         $datarray['last-child'] = 1;
2479                                 }
2480
2481
2482                                 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2483                                         dbesc($item_id),
2484                                         intval($importer['uid'])
2485                                 );
2486
2487                                 // Update content if 'updated' changes
2488
2489                                 if(count($r)) {
2490                                         if (edited_timestamp_is_newer($r[0], $datarray)) {
2491
2492                                                 // do not accept (ignore) an earlier edit than one we currently have.
2493                                                 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2494                                                         continue;
2495
2496                                                 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2497                                                         dbesc($datarray['title']),
2498                                                         dbesc($datarray['body']),
2499                                                         dbesc($datarray['tag']),
2500                                                         dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2501                                                         dbesc(datetime_convert()),
2502                                                         dbesc($item_id),
2503                                                         intval($importer['uid'])
2504                                                 );
2505                                                 create_tags_from_itemuri($item_id, $importer['uid']);
2506                                                 update_thread_uri($item_id, $importer['uid']);
2507                                         }
2508
2509                                         // update last-child if it changes
2510
2511                                         $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2512                                         if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2513                                                 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2514                                                         intval($allow[0]['data']),
2515                                                         dbesc(datetime_convert()),
2516                                                         dbesc($item_id),
2517                                                         intval($importer['uid'])
2518                                                 );
2519                                                 update_thread_uri($item_id, $importer['uid']);
2520                                         }
2521                                         continue;
2522                                 }
2523
2524                                 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2525                                         logger('consume-feed: New follower');
2526                                         new_follower($importer,$contact,$datarray,$item);
2527                                         return;
2528                                 }
2529                                 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW))  {
2530                                         lose_follower($importer,$contact,$datarray,$item);
2531                                         return;
2532                                 }
2533
2534                                 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2535                                         logger('consume-feed: New friend request');
2536                                         new_follower($importer,$contact,$datarray,$item,true);
2537                                         return;
2538                                 }
2539                                 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND))  {
2540                                         lose_sharer($importer,$contact,$datarray,$item);
2541                                         return;
2542                                 }
2543
2544
2545                                 if(! is_array($contact))
2546                                         return;
2547
2548
2549                                 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2550                                                 // one way feed - no remote comment ability
2551                                                 $datarray['last-child'] = 0;
2552                                 }
2553                                 if($contact['network'] === NETWORK_FEED)
2554                                         $datarray['private'] = 2;
2555
2556                                 $datarray['parent-uri'] = $item_id;
2557                                 $datarray['uid'] = $importer['uid'];
2558                                 $datarray['contact-id'] = $contact['id'];
2559
2560                                 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2561                                         // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2562                                         // but otherwise there's a possible data mixup on the sender's system.
2563                                         // the tgroup delivery code called from item_store will correct it if it's a forum,
2564                                         // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2565                                         logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2566                                         $datarray['owner-name']   = $contact['name'];
2567                                         $datarray['owner-link']   = $contact['url'];
2568                                         $datarray['owner-avatar'] = $contact['thumb'];
2569                                 }
2570
2571                                 // We've allowed "followers" to reach this point so we can decide if they are
2572                                 // posting an @-tag delivery, which followers are allowed to do for certain
2573                                 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2574
2575                                 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2576                                         continue;
2577
2578                                 // This is my contact on another system, but it's really me.
2579                                 // Turn this into a wall post.
2580
2581                                 if($contact['remote_self']) {
2582                                         if ($contact['remote_self'] == 2) {
2583                                                 $r = q("SELECT `id`,`url`,`name`,`photo`,`network` FROM `contact` WHERE `uid` = %d AND `self`", intval($importer['uid']));
2584                                                 if (count($r)) {
2585                                                         $datarray['contact-id'] = $r[0]["id"];
2586
2587                                                         $datarray['owner-name'] = $r[0]["name"];
2588                                                         $datarray['owner-link'] = $r[0]["url"];
2589                                                         $datarray['owner-avatar'] = $r[0]["photo"];
2590
2591                                                         $datarray['author-name']   = $datarray['owner-name'];
2592                                                         $datarray['author-link']   = $datarray['owner-link'];
2593                                                         $datarray['author-avatar'] = $datarray['owner-avatar'];
2594                                                 }
2595                                         }
2596
2597                                         if (!isset($datarray["app"]) OR ($datarray["app"] == ""))
2598                                                 $datarray["app"] = network_to_name($contact['network']);
2599
2600                                         $notify = true;
2601                                         if($contact['network'] === NETWORK_FEED) {
2602                                                 $datarray['private'] = 0;
2603                                         }
2604                                 } else
2605                                         $notify = false;
2606
2607                                 $r = item_store($datarray, false, $notify);
2608                                 continue;
2609
2610                         }
2611                 }
2612         }
2613 }
2614
2615 function local_delivery($importer,$data) {
2616         $a = get_app();
2617
2618     logger(__function__, LOGGER_TRACE);
2619
2620         if($importer['readonly']) {
2621                 // We aren't receiving stuff from this person. But we will quietly ignore them
2622                 // rather than a blatant "go away" message.
2623                 logger('local_delivery: ignoring');
2624                 return 0;
2625                 //NOTREACHED
2626         }
2627
2628         // Consume notification feed. This may differ from consuming a public feed in several ways
2629         // - might contain email or friend suggestions
2630         // - might contain remote followup to our message
2631         //              - in which case we need to accept it and then notify other conversants
2632         // - we may need to send various email notifications
2633
2634         $feed = new SimplePie();
2635         $feed->set_raw_data($data);
2636         $feed->enable_order_by_date(false);
2637         $feed->init();
2638
2639
2640         if($feed->error())
2641                 logger('local_delivery: Error parsing XML: ' . $feed->error());
2642
2643
2644         // Check at the feed level for updated contact name and/or photo
2645
2646         $name_updated  = '';
2647         $new_name = '';
2648         $photo_timestamp = '';
2649         $photo_url = '';
2650
2651
2652         $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2653
2654 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2655 //      if(! $rawtags)
2656 //              $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2657
2658         if($rawtags) {
2659                 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2660                 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2661                         $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2662                         $new_name = $elems['name'][0]['data'];
2663                 }
2664                 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2665                         $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2666                         $photo_url = $elems['link'][0]['attribs']['']['href'];
2667                 }
2668         }
2669
2670         if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2671                 logger('local_delivery: Updating photo for ' . $importer['name']);
2672                 require_once("include/Photo.php");
2673                 $photo_failure = false;
2674                 $have_photo = false;
2675
2676                 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2677                         intval($importer['id']),
2678                         intval($importer['importer_uid'])
2679                 );
2680                 if(count($r)) {
2681                         $resource_id = $r[0]['resource-id'];
2682                         $have_photo = true;
2683                 }
2684                 else {
2685                         $resource_id = photo_new_resource();
2686                 }
2687
2688                 $img_str = fetch_url($photo_url,true);
2689                 // guess mimetype from headers or filename
2690                 $type = guess_image_type($photo_url,true);
2691
2692
2693                 $img = new Photo($img_str, $type);
2694                 if($img->is_valid()) {
2695                         if($have_photo) {
2696                                 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2697                                         dbesc($resource_id),
2698                                         intval($importer['id']),
2699                                         intval($importer['importer_uid'])
2700                                 );
2701                         }
2702
2703                         $img->scaleImageSquare(175);
2704
2705                         $hash = $resource_id;
2706                         $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2707
2708                         $img->scaleImage(80);
2709                         $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2710
2711                         $img->scaleImage(48);
2712                         $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2713
2714                         $a = get_app();
2715
2716                         q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2717                                 WHERE `uid` = %d AND `id` = %d",
2718                                 dbesc(datetime_convert()),
2719                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2720                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2721                                 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2722                                 intval($importer['importer_uid']),
2723                                 intval($importer['id'])
2724                         );
2725                 }
2726         }
2727
2728         if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2729                 $r = q("select * from contact where uid = %d and id = %d limit 1",
2730                         intval($importer['importer_uid']),
2731                         intval($importer['id'])
2732                 );
2733
2734                 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2735                         dbesc(notags(trim($new_name))),
2736                         dbesc(datetime_convert()),
2737                         intval($importer['importer_uid']),
2738                         intval($importer['id'])
2739                 );
2740
2741                 // do our best to update the name on content items
2742
2743                 if(count($r)) {
2744                         q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2745                                 dbesc(notags(trim($new_name))),
2746                                 dbesc($r[0]['name']),
2747                                 dbesc($r[0]['url']),
2748                                 intval($importer['importer_uid'])
2749                         );
2750                 }
2751         }
2752
2753
2754
2755         // Currently unsupported - needs a lot of work
2756         $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2757         if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2758                 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2759                 $newloc = array();
2760                 $newloc['uid'] = $importer['importer_uid'];
2761                 $newloc['cid'] = $importer['id'];
2762                 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2763                 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2764                 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2765                 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2766                 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2767                 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2768                 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2769                 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2770                 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2771                 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2772                 /** relocated user must have original key pair */
2773                 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2774                 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2775
2776                 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
2777
2778                 // update contact
2779                 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
2780                         intval($importer['id']),
2781                         intval($importer['importer_uid']));
2782                 if ($r === false)
2783                         return 1;
2784                 $old = $r[0];
2785
2786                 $x = q("UPDATE contact SET
2787                                         name = '%s',
2788                                         photo = '%s',
2789                                         thumb = '%s',
2790                                         micro = '%s',
2791                                         url = '%s',
2792                                         request = '%s',
2793                                         confirm = '%s',
2794                                         notify = '%s',
2795                                         poll = '%s',
2796                                         `site-pubkey` = '%s'
2797                         WHERE id=%d AND uid=%d;",
2798                                         dbesc($newloc['name']),
2799                                         dbesc($newloc['photo']),
2800                                         dbesc($newloc['thumb']),
2801                                         dbesc($newloc['micro']),
2802                                         dbesc($newloc['url']),
2803                                         dbesc($newloc['request']),
2804                                         dbesc($newloc['confirm']),
2805                                         dbesc($newloc['notify']),
2806                                         dbesc($newloc['poll']),
2807                                         dbesc($newloc['sitepubkey']),
2808                                         intval($importer['id']),
2809                                         intval($importer['importer_uid']));
2810
2811                 if ($x === false)
2812                         return 1;
2813                 // update items
2814                 $fields = array(
2815                         'owner-link' => array($old['url'], $newloc['url']),
2816                         'author-link' => array($old['url'], $newloc['url']),
2817                         'owner-avatar' => array($old['photo'], $newloc['photo']),
2818                         'author-avatar' => array($old['photo'], $newloc['photo']),
2819                         );
2820                 foreach ($fields as $n=>$f){
2821                         $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
2822                                         $n, dbesc($f[1]),
2823                                         $n, dbesc($f[0]),
2824                                         intval($importer['importer_uid']));
2825                                 if ($x === false)
2826                                         return 1;
2827                         }
2828
2829                 // TODO
2830                 // merge with current record, current contents have priority
2831                 // update record, set url-updated
2832                 // update profile photos
2833                 // schedule a scan?
2834                 return 0;
2835         }
2836
2837
2838         // handle friend suggestion notification
2839
2840         $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
2841         if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
2842                 $base = $sugg[0]['child'][NAMESPACE_DFRN];
2843                 $fsugg = array();
2844                 $fsugg['uid'] = $importer['importer_uid'];
2845                 $fsugg['cid'] = $importer['id'];
2846                 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
2847                 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
2848                 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
2849                 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
2850                 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
2851
2852                 // Does our member already have a friend matching this description?
2853
2854                 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
2855                         dbesc($fsugg['name']),
2856                         dbesc(normalise_link($fsugg['url'])),
2857                         intval($fsugg['uid'])
2858                 );
2859                 if(count($r))
2860                         return 0;
2861
2862                 // Do we already have an fcontact record for this person?
2863
2864                 $fid = 0;
2865                 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2866                         dbesc($fsugg['url']),
2867                         dbesc($fsugg['name']),
2868                         dbesc($fsugg['request'])
2869                 );
2870                 if(count($r)) {
2871                         $fid = $r[0]['id'];
2872
2873                         // OK, we do. Do we already have an introduction for this person ?
2874                         $r = q("select id from intro where uid = %d and fid = %d limit 1",
2875                                 intval($fsugg['uid']),
2876                                 intval($fid)
2877                         );
2878                         if(count($r))
2879                                 return 0;
2880                 }
2881                 if(! $fid)
2882                         $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
2883                         dbesc($fsugg['name']),
2884                         dbesc($fsugg['url']),
2885                         dbesc($fsugg['photo']),
2886                         dbesc($fsugg['request'])
2887                 );
2888                 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
2889                         dbesc($fsugg['url']),
2890                         dbesc($fsugg['name']),
2891                         dbesc($fsugg['request'])
2892                 );
2893                 if(count($r)) {
2894                         $fid = $r[0]['id'];
2895                 }
2896                 // database record did not get created. Quietly give up.
2897                 else
2898                         return 0;
2899
2900
2901                 $hash = random_string();
2902
2903                 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
2904                         VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
2905                         intval($fsugg['uid']),
2906                         intval($fid),
2907                         intval($fsugg['cid']),
2908                         dbesc($fsugg['body']),
2909                         dbesc($hash),
2910                         dbesc(datetime_convert()),
2911                         intval(0)
2912                 );
2913
2914                 notification(array(
2915                         'type'         => NOTIFY_SUGGEST,
2916                         'notify_flags' => $importer['notify-flags'],
2917                         'language'     => $importer['language'],
2918                         'to_name'      => $importer['username'],
2919                         'to_email'     => $importer['email'],
2920                         'uid'          => $importer['importer_uid'],
2921                         'item'         => $fsugg,
2922                         'link'         => $a->get_baseurl() . '/notifications/intros',
2923                         'source_name'  => $importer['name'],
2924                         'source_link'  => $importer['url'],
2925                         'source_photo' => $importer['photo'],
2926                         'verb'         => ACTIVITY_REQ_FRIEND,
2927                         'otype'        => 'intro'
2928                 ));
2929
2930                 return 0;
2931         }
2932
2933         $ismail = false;
2934
2935         $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
2936         if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
2937
2938                 logger('local_delivery: private message received');
2939
2940                 $ismail = true;
2941                 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
2942
2943                 $msg = array();
2944                 $msg['uid'] = $importer['importer_uid'];
2945                 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
2946                 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
2947                 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
2948                 $msg['contact-id'] = $importer['id'];
2949                 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
2950                 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
2951                 $msg['seen'] = 0;
2952                 $msg['replied'] = 0;
2953                 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
2954                 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
2955                 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
2956
2957                 dbesc_array($msg);
2958
2959                 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
2960                         . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
2961
2962                 // send notifications.
2963
2964                 require_once('include/enotify.php');
2965
2966                 $notif_params = array(
2967                         'type' => NOTIFY_MAIL,
2968                         'notify_flags' => $importer['notify-flags'],
2969                         'language' => $importer['language'],
2970                         'to_name' => $importer['username'],
2971                         'to_email' => $importer['email'],
2972                         'uid' => $importer['importer_uid'],
2973                         'item' => $msg,
2974                         'source_name' => $msg['from-name'],
2975                         'source_link' => $importer['url'],
2976                         'source_photo' => $importer['thumb'],
2977                         'verb' => ACTIVITY_POST,
2978                         'otype' => 'mail'
2979                 );
2980
2981                 notification($notif_params);
2982                 return 0;
2983
2984                 // NOTREACHED
2985         }
2986
2987         $community_page = 0;
2988         $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2989         if($rawtags) {
2990                 $community_page = intval($rawtags[0]['data']);
2991         }
2992         if(intval($importer['forum']) != $community_page) {
2993                 q("update contact set forum = %d where id = %d",
2994                         intval($community_page),
2995                         intval($importer['id'])
2996                 );
2997                 $importer['forum'] = (string) $community_page;
2998         }
2999
3000         logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3001
3002         // process any deleted entries
3003
3004         $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3005         if(is_array($del_entries) && count($del_entries)) {
3006                 foreach($del_entries as $dentry) {
3007                         $deleted = false;
3008                         if(isset($dentry['attribs']['']['ref'])) {
3009                                 $uri = $dentry['attribs']['']['ref'];
3010                                 $deleted = true;
3011                                 if(isset($dentry['attribs']['']['when'])) {
3012                                         $when = $dentry['attribs']['']['when'];
3013                                         $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3014                                 }
3015                                 else
3016                                         $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3017                         }
3018                         if($deleted) {
3019
3020                                 // check for relayed deletes to our conversation
3021
3022                                 $is_reply = false;
3023                                 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3024                                         dbesc($uri),
3025                                         intval($importer['importer_uid'])
3026                                 );
3027                                 if(count($r)) {
3028                                         $parent_uri = $r[0]['parent-uri'];
3029                                         if($r[0]['id'] != $r[0]['parent'])
3030                                                 $is_reply = true;
3031                                 }
3032
3033                                 if($is_reply) {
3034                                         $community = false;
3035
3036                                         if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3037                                                 $sql_extra = '';
3038                                                 $community = true;
3039                                                 logger('local_delivery: possible community delete');
3040                                         }
3041                                         else
3042                                                 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3043
3044                                         // was the top-level post for this reply written by somebody on this site?
3045                                         // Specifically, the recipient?
3046
3047                                         $is_a_remote_delete = false;
3048
3049                                         // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3050                                         $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3051                                                 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3052                                                 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3053                                                 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3054                                                 AND `item`.`uid` = %d
3055                                                 $sql_extra
3056                                                 LIMIT 1",
3057                                                 dbesc($parent_uri),
3058                                                 dbesc($parent_uri),
3059                                                 dbesc($parent_uri),
3060                                                 intval($importer['importer_uid'])
3061                                         );
3062                                         if($r && count($r))
3063                                                 $is_a_remote_delete = true;
3064
3065                                         // Does this have the characteristics of a community or private group comment?
3066                                         // If it's a reply to a wall post on a community/prvgroup page it's a
3067                                         // valid community comment. Also forum_mode makes it valid for sure.
3068                                         // If neither, it's not.
3069
3070                                         if($is_a_remote_delete && $community) {
3071                                                 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3072                                                         $is_a_remote_delete = false;
3073                                                         logger('local_delivery: not a community delete');
3074                                                 }
3075                                         }
3076
3077                                         if($is_a_remote_delete) {
3078                                                 logger('local_delivery: received remote delete');
3079                                         }
3080                                 }
3081
3082                                 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3083                                         WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3084                                         dbesc($uri),
3085                                         intval($importer['importer_uid']),
3086                                         intval($importer['id'])
3087                                 );
3088
3089                                 if(count($r)) {
3090                                         $item = $r[0];
3091
3092                                         if($item['deleted'])
3093                                                 continue;
3094
3095                                         logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3096
3097                                         if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3098                                                 $xo = parse_xml_string($item['object'],false);
3099                                                 $xt = parse_xml_string($item['target'],false);
3100
3101                                                 if($xt->type === ACTIVITY_OBJ_NOTE) {
3102                                                         $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3103                                                                 dbesc($xt->id),
3104                                                                 intval($importer['importer_uid'])
3105                                                         );
3106                                                         if(count($i)) {
3107
3108                                                                 // For tags, the owner cannot remove the tag on the author's copy of the post.
3109
3110                                                                 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3111                                                                 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3112                                                                 $author_copy = (($item['origin']) ? true : false);
3113
3114                                                                 if($owner_remove && $author_copy)
3115                                                                         continue;
3116                                                                 if($author_remove || $owner_remove) {
3117                                                                         $tags = explode(',',$i[0]['tag']);
3118                                                                         $newtags = array();
3119                                                                         if(count($tags)) {
3120                                                                                 foreach($tags as $tag)
3121                                                                                         if(trim($tag) !== trim($xo->body))
3122                                                                                                 $newtags[] = trim($tag);
3123                                                                         }
3124                                                                         q("update item set tag = '%s' where id = %d",
3125                                                                                 dbesc(implode(',',$newtags)),
3126                                                                                 intval($i[0]['id'])
3127                                                                         );
3128                                                                         create_tags_from_item($i[0]['id']);
3129                                                                 }
3130                                                         }
3131                                                 }
3132                                         }
3133
3134                                         if($item['uri'] == $item['parent-uri']) {
3135                                                 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3136                                                         `body` = '', `title` = ''
3137                                                         WHERE `parent-uri` = '%s' AND `uid` = %d",
3138                                                         dbesc($when),
3139                                                         dbesc(datetime_convert()),
3140                                                         dbesc($item['uri']),
3141                                                         intval($importer['importer_uid'])
3142                                                 );
3143                                                 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3144                                                 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3145                                                 update_thread_uri($item['uri'], $importer['importer_uid']);
3146                                         }
3147                                         else {
3148                                                 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3149                                                         `body` = '', `title` = ''
3150                                                         WHERE `uri` = '%s' AND `uid` = %d",
3151                                                         dbesc($when),
3152                                                         dbesc(datetime_convert()),
3153                                                         dbesc($uri),
3154                                                         intval($importer['importer_uid'])
3155                                                 );
3156                                                 create_tags_from_itemuri($uri, $importer['importer_uid']);
3157                                                 create_files_from_itemuri($uri, $importer['importer_uid']);
3158                                                 update_thread_uri($uri, $importer['importer_uid']);
3159                                                 if($item['last-child']) {
3160                                                         // ensure that last-child is set in case the comment that had it just got wiped.
3161                                                         q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3162                                                                 dbesc(datetime_convert()),
3163                                                                 dbesc($item['parent-uri']),
3164                                                                 intval($item['uid'])
3165                                                         );
3166                                                         // who is the last child now?
3167                                                         $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3168                                                                 ORDER BY `created` DESC LIMIT 1",
3169                                                                         dbesc($item['parent-uri']),
3170                                                                         intval($importer['importer_uid'])
3171                                                         );
3172                                                         if(count($r)) {
3173                                                                 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3174                                                                         intval($r[0]['id'])
3175                                                                 );
3176                                                         }
3177                                                 }
3178                                                 // if this is a relayed delete, propagate it to other recipients
3179
3180                                                 if($is_a_remote_delete)
3181                                                         proc_run('php',"include/notifier.php","drop",$item['id']);
3182                                         }
3183                                 }
3184                         }
3185                 }
3186         }
3187
3188
3189         foreach($feed->get_items() as $item) {
3190
3191                 $is_reply = false;
3192                 $item_id = $item->get_id();
3193                 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3194                 if(isset($rawthread[0]['attribs']['']['ref'])) {
3195                         $is_reply = true;
3196                         $parent_uri = $rawthread[0]['attribs']['']['ref'];
3197                 }
3198
3199                 if($is_reply) {
3200                         $community = false;
3201
3202                         if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3203                                 $sql_extra = '';
3204                                 $community = true;
3205                                 logger('local_delivery: possible community reply');
3206                         }
3207                         else
3208                                 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3209
3210                         // was the top-level post for this reply written by somebody on this site?
3211                         // Specifically, the recipient?
3212
3213                         $is_a_remote_comment = false;
3214                         $top_uri = $parent_uri;
3215
3216                         $r = q("select `item`.`parent-uri` from `item`
3217                                 WHERE `item`.`uri` = '%s'
3218                                 LIMIT 1",
3219                                 dbesc($parent_uri)
3220                         );
3221                         if($r && count($r)) {
3222                                 $top_uri = $r[0]['parent-uri'];
3223
3224                                 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3225                                 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3226                                         `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3227                                         INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3228                                         WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3229                                         AND `item`.`uid` = %d
3230                                         $sql_extra
3231                                         LIMIT 1",
3232                                         dbesc($top_uri),
3233                                         dbesc($top_uri),
3234                                         dbesc($top_uri),
3235                                         intval($importer['importer_uid'])
3236                                 );
3237                                 if($r && count($r))
3238                                         $is_a_remote_comment = true;
3239                         }
3240
3241                         // Does this have the characteristics of a community or private group comment?
3242                         // If it's a reply to a wall post on a community/prvgroup page it's a
3243                         // valid community comment. Also forum_mode makes it valid for sure.
3244                         // If neither, it's not.
3245
3246                         if($is_a_remote_comment && $community) {
3247                                 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3248                                         $is_a_remote_comment = false;
3249                                         logger('local_delivery: not a community reply');
3250                                 }
3251                         }
3252
3253                         if($is_a_remote_comment) {
3254                                 logger('local_delivery: received remote comment');
3255                                 $is_like = false;
3256                                 // remote reply to our post. Import and then notify everybody else.
3257
3258                                 $datarray = get_atom_elements($feed, $item);
3259
3260                                 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body`  FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3261                                         dbesc($item_id),
3262                                         intval($importer['importer_uid'])
3263                                 );
3264
3265                                 // Update content if 'updated' changes
3266
3267                                 if(count($r)) {
3268                                         $iid = $r[0]['id'];
3269                                         if (edited_timestamp_is_newer($r[0], $datarray)) {
3270
3271                                                 // do not accept (ignore) an earlier edit than one we currently have.
3272                                                 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3273                                                         continue;
3274
3275                                                 logger('received updated comment' , LOGGER_DEBUG);
3276                                                 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3277                                                         dbesc($datarray['title']),
3278                                                         dbesc($datarray['body']),
3279                                                         dbesc($datarray['tag']),
3280                                                         dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3281                                                         dbesc(datetime_convert()),
3282                                                         dbesc($item_id),
3283                                                         intval($importer['importer_uid'])
3284                                                 );
3285                                                 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3286
3287                                                 proc_run('php',"include/notifier.php","comment-import",$iid);
3288
3289                                         }
3290
3291                                         continue;
3292                                 }
3293
3294
3295
3296                                 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3297                                         intval($importer['importer_uid'])
3298                                 );
3299
3300
3301                                 $datarray['type'] = 'remote-comment';
3302                                 $datarray['wall'] = 1;
3303                                 $datarray['parent-uri'] = $parent_uri;
3304                                 $datarray['uid'] = $importer['importer_uid'];
3305                                 $datarray['owner-name'] = $own[0]['name'];
3306                                 $datarray['owner-link'] = $own[0]['url'];
3307                                 $datarray['owner-avatar'] = $own[0]['thumb'];
3308                                 $datarray['contact-id'] = $importer['id'];
3309
3310                                 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3311                                         $is_like = true;
3312                                         $datarray['type'] = 'activity';
3313                                         $datarray['gravity'] = GRAVITY_LIKE;
3314                                         $datarray['last-child'] = 0;
3315                                         // only one like or dislike per person
3316                                         // splitted into two queries for performance issues
3317                                         $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",
3318                                                 intval($datarray['uid']),
3319                                                 intval($datarray['contact-id']),
3320                                                 dbesc($datarray['verb']),
3321                                                 dbesc($datarray['parent-uri'])
3322
3323                                         );
3324                                         if($r && count($r))
3325                                                 continue;
3326
3327                                         $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",
3328                                                 intval($datarray['uid']),
3329                                                 intval($datarray['contact-id']),
3330                                                 dbesc($datarray['verb']),
3331                                                 dbesc($datarray['parent-uri'])
3332
3333                                         );
3334                                         if($r && count($r))
3335                                                 continue;
3336                                 }
3337
3338                                 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3339
3340                                         $xo = parse_xml_string($datarray['object'],false);
3341                                         $xt = parse_xml_string($datarray['target'],false);
3342
3343                                         if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3344
3345                                                 // fetch the parent item
3346
3347                                                 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3348                                                         dbesc($xt->id),
3349                                                         intval($importer['importer_uid'])
3350                                                 );
3351                                                 if(! count($tagp))
3352                                                         continue;
3353
3354                                                 // extract tag, if not duplicate, and this user allows tags, add to parent item
3355
3356                                                 if($xo->id && $xo->content) {
3357                                                         $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3358                                                         if(! (stristr($tagp[0]['tag'],$newtag))) {
3359                                                                 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3360                                                                         intval($importer['importer_uid'])
3361                                                                 );
3362                                                                 if(count($i) && ! intval($i[0]['blocktags'])) {
3363                                                                         q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3364                                                                                 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3365                                                                                 intval($tagp[0]['id']),
3366                                                                                 dbesc(datetime_convert()),
3367                                                                                 dbesc(datetime_convert())
3368                                                                         );
3369                                                                         create_tags_from_item($tagp[0]['id']);
3370                                                                 }
3371                                                         }
3372                                                 }
3373                                         }
3374                                 }
3375
3376
3377                                 $posted_id = item_store($datarray);
3378                                 $parent = 0;
3379
3380                                 if($posted_id) {
3381                                         $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3382                                                 intval($posted_id),
3383                                                 intval($importer['importer_uid'])
3384                                         );
3385                                         if(count($r)) {
3386                                                 $parent = $r[0]['parent'];
3387                                                 $parent_uri = $r[0]['parent-uri'];
3388                                         }
3389
3390                                         if(! $is_like) {
3391                                                 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3392                                                         dbesc(datetime_convert()),
3393                                                         intval($importer['importer_uid']),
3394                                                         intval($r[0]['parent'])
3395                                                 );
3396
3397                                                 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3398                                                         dbesc(datetime_convert()),
3399                                                         intval($importer['importer_uid']),
3400                                                         intval($posted_id)
3401                                                 );
3402                                         }
3403
3404                                         if($posted_id && $parent) {
3405
3406                                                 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3407
3408                                                 if((! $is_like) && (! $importer['self'])) {
3409
3410                                                         require_once('include/enotify.php');
3411
3412                                                         notification(array(
3413                                                                 'type'         => NOTIFY_COMMENT,
3414                                                                 'notify_flags' => $importer['notify-flags'],
3415                                                                 'language'     => $importer['language'],
3416                                                                 'to_name'      => $importer['username'],
3417                                                                 'to_email'     => $importer['email'],
3418                                                                 'uid'          => $importer['importer_uid'],
3419                                                                 'item'         => $datarray,
3420                                                                 'link'             => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3421                                                                 'source_name'  => stripslashes($datarray['author-name']),
3422                                                                 'source_link'  => $datarray['author-link'],
3423                                                                 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3424                                                                         ? $importer['thumb'] : $datarray['author-avatar']),
3425                                                                 'verb'         => ACTIVITY_POST,
3426                                                                 'otype'        => 'item',
3427                                                                 'parent'       => $parent,
3428                                                                 'parent_uri'   => $parent_uri,
3429                                                         ));
3430
3431                                                 }
3432                                         }
3433
3434                                         return 0;
3435                                         // NOTREACHED
3436                                 }
3437                         }
3438                         else {
3439
3440                                 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3441
3442                                 $item_id  = $item->get_id();
3443                                 $datarray = get_atom_elements($feed,$item);
3444
3445                                 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3446                                         continue;
3447
3448                                 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3449                                         dbesc($item_id),
3450                                         intval($importer['importer_uid'])
3451                                 );
3452
3453                                 // Update content if 'updated' changes
3454
3455                                 if(count($r)) {
3456                                         if (edited_timestamp_is_newer($r[0], $datarray)) {
3457
3458                                                 // do not accept (ignore) an earlier edit than one we currently have.
3459                                                 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3460                                                         continue;
3461
3462                                                 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3463                                                         dbesc($datarray['title']),
3464                                                         dbesc($datarray['body']),
3465                                                         dbesc($datarray['tag']),
3466                                                         dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3467                                                         dbesc(datetime_convert()),
3468                                                         dbesc($item_id),
3469                                                         intval($importer['importer_uid'])
3470                                                 );
3471                                                 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3472                                         }
3473
3474                                         // update last-child if it changes
3475
3476                                         $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3477                                         if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3478                                                 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3479                                                         dbesc(datetime_convert()),
3480                                                         dbesc($parent_uri),
3481                                                         intval($importer['importer_uid'])
3482                                                 );
3483                                                 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s'  WHERE `uri` = '%s' AND `uid` = %d",
3484                                                         intval($allow[0]['data']),
3485                                                         dbesc(datetime_convert()),
3486                                                         dbesc($item_id),
3487                                                         intval($importer['importer_uid'])
3488                                                 );
3489                                         }
3490                                         continue;
3491                                 }
3492
3493                                 $datarray['parent-uri'] = $parent_uri;
3494                                 $datarray['uid'] = $importer['importer_uid'];
3495                                 $datarray['contact-id'] = $importer['id'];
3496                                 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3497                                         $datarray['type'] = 'activity';
3498                                         $datarray['gravity'] = GRAVITY_LIKE;
3499                                         // only one like or dislike per person
3500                                         // splitted into two queries for performance issues
3501                                         $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",
3502                                                 intval($datarray['uid']),
3503                                                 intval($datarray['contact-id']),
3504                                                 dbesc($datarray['verb']),
3505                                                 dbesc($parent_uri)
3506                                         );
3507                                         if($r && count($r))
3508                                                 continue;
3509
3510                                         $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",
3511                                                 intval($datarray['uid']),
3512                                                 intval($datarray['contact-id']),
3513                                                 dbesc($datarray['verb']),
3514                                                 dbesc($parent_uri)
3515                                         );
3516                                         if($r && count($r))
3517                                                 continue;
3518
3519                                 }
3520
3521                                 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3522
3523                                         $xo = parse_xml_string($datarray['object'],false);
3524                                         $xt = parse_xml_string($datarray['target'],false);
3525
3526                                         if($xt->type == ACTIVITY_OBJ_NOTE) {
3527                                                 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3528                                                         dbesc($xt->id),
3529                                                         intval($importer['importer_uid'])
3530                                                 );
3531                                                 if(! count($r))
3532                                                         continue;
3533
3534                                                 // extract tag, if not duplicate, add to parent item
3535                                                 if($xo->content) {
3536                                                         if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3537                                                                 q("UPDATE item SET tag = '%s' WHERE id = %d",
3538                                                                         dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3539                                                                         intval($r[0]['id'])
3540                                                                 );
3541                                                                 create_tags_from_item($r[0]['id']);
3542                                                         }
3543                                                 }
3544                                         }
3545                                 }
3546
3547                                 $posted_id = item_store($datarray);
3548
3549                                 // find out if our user is involved in this conversation and wants to be notified.
3550
3551                                 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3552
3553                                         $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3554                                                 dbesc($top_uri),
3555                                                 intval($importer['importer_uid'])
3556                                         );
3557
3558                                         if(count($myconv)) {
3559                                                 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3560
3561                                                 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3562                                                 if(! link_compare($datarray['author-link'],$importer_url)) {
3563
3564
3565                                                         foreach($myconv as $conv) {
3566
3567                                                                 // now if we find a match, it means we're in this conversation
3568
3569                                                                 if(! link_compare($conv['author-link'],$importer_url))
3570                                                                         continue;
3571
3572                                                                 require_once('include/enotify.php');
3573
3574                                                                 $conv_parent = $conv['parent'];
3575
3576                                                                 notification(array(
3577                                                                         'type'         => NOTIFY_COMMENT,
3578                                                                         'notify_flags' => $importer['notify-flags'],
3579                                                                         'language'     => $importer['language'],
3580                                                                         'to_name'      => $importer['username'],
3581                                                                         'to_email'     => $importer['email'],
3582                                                                         'uid'          => $importer['importer_uid'],
3583                                                                         'item'         => $datarray,
3584                                                                         'link'             => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3585                                                                         'source_name'  => stripslashes($datarray['author-name']),
3586                                                                         'source_link'  => $datarray['author-link'],
3587                                                                         'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3588                                                                                 ? $importer['thumb'] : $datarray['author-avatar']),
3589                                                                         'verb'         => ACTIVITY_POST,
3590                                                                         'otype'        => 'item',
3591                                                                         'parent'       => $conv_parent,
3592                                                                         'parent_uri'   => $parent_uri
3593
3594                                                                 ));
3595
3596                                                                 // only send one notification
3597                                                                 break;
3598                                                         }
3599                                                 }
3600                                         }
3601                                 }
3602                                 continue;
3603                         }
3604                 }
3605
3606                 else {
3607
3608                         // Head post of a conversation. Have we seen it? If not, import it.
3609
3610
3611                         $item_id  = $item->get_id();
3612                         $datarray = get_atom_elements($feed,$item);
3613
3614                         if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3615                                 $ev = bbtoevent($datarray['body']);
3616                                 if(x($ev,'desc') && x($ev,'start')) {
3617                                         $ev['cid'] = $importer['id'];
3618                                         $ev['uid'] = $importer['uid'];
3619                                         $ev['uri'] = $item_id;
3620                                         $ev['edited'] = $datarray['edited'];
3621                                         $ev['private'] = $datarray['private'];
3622
3623                                         $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3624                                                 dbesc($item_id),
3625                                                 intval($importer['uid'])
3626                                         );
3627                                         if(count($r))
3628                                                 $ev['id'] = $r[0]['id'];
3629                                         $xyz = event_store($ev);
3630                                         continue;
3631                                 }
3632                         }
3633
3634                         $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3635                                 dbesc($item_id),
3636                                 intval($importer['importer_uid'])
3637                         );
3638
3639                         // Update content if 'updated' changes
3640
3641                         if(count($r)) {
3642                                 if (edited_timestamp_is_newer($r[0], $datarray)) {
3643
3644                                         // do not accept (ignore) an earlier edit than one we currently have.
3645                                         if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3646                                                 continue;
3647
3648                                         $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3649                                                 dbesc($datarray['title']),
3650                                                 dbesc($datarray['body']),
3651                                                 dbesc($datarray['tag']),
3652                                                 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3653                                                 dbesc(datetime_convert()),
3654                                                 dbesc($item_id),
3655                                                 intval($importer['importer_uid'])
3656                                         );
3657                                         create_tags_from_itemuri($item_id, $importer['importer_uid']);
3658                                         update_thread_uri($item_id, $importer['importer_uid']);
3659                                 }
3660
3661                                 // update last-child if it changes
3662
3663                                 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3664                                 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3665                                         $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3666                                                 intval($allow[0]['data']),
3667                                                 dbesc(datetime_convert()),
3668                                                 dbesc($item_id),
3669                                                 intval($importer['importer_uid'])
3670                                         );
3671                                 }
3672                                 continue;
3673                         }
3674
3675                         $datarray['parent-uri'] = $item_id;
3676                         $datarray['uid'] = $importer['importer_uid'];
3677                         $datarray['contact-id'] = $importer['id'];
3678
3679
3680                         if(! link_compare($datarray['owner-link'],$importer['url'])) {
3681                                 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3682                                 // but otherwise there's a possible data mixup on the sender's system.
3683                                 // the tgroup delivery code called from item_store will correct it if it's a forum,
3684                                 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3685                                 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3686                                 $datarray['owner-name']   = $importer['senderName'];
3687                                 $datarray['owner-link']   = $importer['url'];
3688                                 $datarray['owner-avatar'] = $importer['thumb'];
3689                         }
3690
3691                         if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3692                                 continue;
3693
3694                         // This is my contact on another system, but it's really me.
3695                         // Turn this into a wall post.
3696
3697                         if($importer['remote_self']) {
3698                                 if ($importer['remote_self'] == 2) {
3699                                         $r = q("SELECT `id`,`url`,`name`,`photo`,`network` FROM `contact` WHERE `uid` = %d AND `self`",
3700                                                 intval($importer['importer_uid']));
3701                                         if (count($r)) {
3702                                                 $datarray['contact-id'] = $r[0]["id"];
3703
3704                                                 $datarray['owner-name'] = $r[0]["name"];
3705                                                 $datarray['owner-link'] = $r[0]["url"];
3706                                                 $datarray['owner-avatar'] = $r[0]["photo"];
3707
3708                                                 $datarray['author-name']   = $datarray['owner-name'];
3709                                                 $datarray['author-link']   = $datarray['owner-link'];
3710                                                 $datarray['author-avatar'] = $datarray['owner-avatar'];
3711                                         }
3712                                 }
3713
3714                                 $notify = true;
3715                         } else
3716                                 $notify = false;
3717
3718                         $posted_id = item_store($datarray, false, $notify);
3719
3720                         if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3721                                 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3722                                 if(! $verb)
3723                                         continue;
3724                                 $xo = parse_xml_string($datarray['object'],false);
3725
3726                                 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3727
3728                                         // somebody was poked/prodded. Was it me?
3729
3730                                         $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3731
3732                                 foreach($links->link as $l) {
3733                                 $atts = $l->attributes();
3734                                 switch($atts['rel']) {
3735                                         case "alternate":
3736                                                                 $Blink = $atts['href'];
3737                                                                 break;
3738                                                         default:
3739                                                                 break;
3740                                     }
3741                                 }
3742                                         if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3743
3744                                                 // send a notification
3745                                                 require_once('include/enotify.php');
3746
3747                                                 notification(array(
3748                                                         'type'         => NOTIFY_POKE,
3749                                                         'notify_flags' => $importer['notify-flags'],
3750                                                         'language'     => $importer['language'],
3751                                                         'to_name'      => $importer['username'],
3752                                                         'to_email'     => $importer['email'],
3753                                                         'uid'          => $importer['importer_uid'],
3754                                                         'item'         => $datarray,
3755                                                         'link'             => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3756                                                         'source_name'  => stripslashes($datarray['author-name']),
3757                                                         'source_link'  => $datarray['author-link'],
3758                                                         'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3759                                                                 ? $importer['thumb'] : $datarray['author-avatar']),
3760                                                         'verb'         => $datarray['verb'],
3761                                                         'otype'        => 'person',
3762                                                         'activity'     => $verb,
3763
3764                                                 ));
3765                                         }
3766                                 }
3767                         }
3768
3769                         continue;
3770                 }
3771         }
3772
3773         return 0;
3774         // NOTREACHED
3775
3776 }
3777
3778
3779 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3780         $url = notags(trim($datarray['author-link']));
3781         $name = notags(trim($datarray['author-name']));
3782         $photo = notags(trim($datarray['author-avatar']));
3783
3784         $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3785         if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3786                 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3787
3788         if(is_array($contact)) {
3789                 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3790                         || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3791                         $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3792                                 intval(CONTACT_IS_FRIEND),
3793                                 intval($contact['id']),
3794                                 intval($importer['uid'])
3795                         );
3796                 }
3797                 // send email notification to owner?
3798         }
3799         else {
3800
3801                 // create contact record
3802
3803                 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
3804                         `blocked`, `readonly`, `pending`, `writable` )
3805                         VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
3806                         intval($importer['uid']),
3807                         dbesc(datetime_convert()),
3808                         dbesc($url),
3809                         dbesc(normalise_link($url)),
3810                         dbesc($name),
3811                         dbesc($nick),
3812                         dbesc($photo),
3813                         dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
3814                         intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
3815                 );
3816                 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
3817                                 intval($importer['uid']),
3818                                 dbesc($url)
3819                 );
3820                 if(count($r))
3821                                 $contact_record = $r[0];
3822
3823                 // create notification
3824                 $hash = random_string();
3825
3826                 if(is_array($contact_record)) {
3827                         $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
3828                                 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
3829                                 intval($importer['uid']),
3830                                 intval($contact_record['id']),
3831                                 dbesc($hash),
3832                                 dbesc(datetime_convert())
3833                         );
3834                 }
3835
3836                 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
3837                         intval($importer['uid'])
3838                 );
3839                 $a = get_app();
3840                 if(count($r)) {
3841
3842                         if(intval($r[0]['def_gid'])) {
3843                                 require_once('include/group.php');
3844                                 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
3845                         }
3846
3847                         if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
3848                                 (($r[0]['page-flags'] == PAGE_NORMAL) OR ($r[0]['page-flags'] == PAGE_SOAPBOX))) {
3849
3850
3851
3852                                 notification(array(
3853                                         'type'         => NOTIFY_INTRO,
3854                                         'notify_flags' => $r[0]['notify-flags'],
3855                                         'language'     => $r[0]['language'],
3856                                         'to_name'      => $r[0]['username'],
3857                                         'to_email'     => $r[0]['email'],
3858                                         'uid'          => $r[0]['uid'],
3859                                         'link'             => $a->get_baseurl() . '/notifications/intro',
3860                                         'source_name'  => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
3861                                         'source_link'  => $contact_record['url'],
3862                                         'source_photo' => $contact_record['photo'],
3863                                         'verb'         => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
3864                                         'otype'        => 'intro'
3865                                 ));
3866
3867
3868                         }
3869                 }
3870         }
3871 }
3872
3873 function lose_follower($importer,$contact,$datarray,$item) {
3874
3875         if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
3876                 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3877                         intval(CONTACT_IS_SHARING),
3878                         intval($contact['id'])
3879                 );
3880         }
3881         else {
3882                 contact_remove($contact['id']);
3883         }
3884 }
3885
3886 function lose_sharer($importer,$contact,$datarray,$item) {
3887
3888         if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
3889                 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
3890                         intval(CONTACT_IS_FOLLOWER),
3891                         intval($contact['id'])
3892                 );
3893         }
3894         else {
3895                 contact_remove($contact['id']);
3896         }
3897 }
3898
3899
3900 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
3901
3902         $a = get_app();
3903
3904         if(is_array($importer)) {
3905                 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
3906                         intval($importer['uid'])
3907                 );
3908         }
3909
3910         // Diaspora has different message-ids in feeds than they do
3911         // through the direct Diaspora protocol. If we try and use
3912         // the feed, we'll get duplicates. So don't.
3913
3914         if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
3915                 return;
3916
3917         $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
3918
3919         // Use a single verify token, even if multiple hubs
3920
3921         $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
3922
3923         $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
3924
3925         logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: '  . $push_url . ' with verifier ' . $verify_token);
3926
3927         if(! strlen($contact['hub-verify'])) {
3928                 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
3929                         dbesc($verify_token),
3930                         intval($contact['id'])
3931                 );
3932         }
3933
3934         post_url($url,$params);
3935
3936         logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
3937
3938         return;
3939
3940 }
3941
3942
3943 function atom_author($tag,$name,$uri,$h,$w,$photo) {
3944         $o = '';
3945         if(! $tag)
3946                 return $o;
3947         $name = xmlify($name);
3948         $uri = xmlify($uri);
3949         $h = intval($h);
3950         $w = intval($w);
3951         $photo = xmlify($photo);
3952
3953
3954         $o .= "<$tag>\r\n";
3955         $o .= "<name>$name</name>\r\n";
3956         $o .= "<uri>$uri</uri>\r\n";
3957         $o .= '<link rel="photo"  type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3958         $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
3959
3960         call_hooks('atom_author', $o);
3961
3962         $o .= "</$tag>\r\n";
3963         return $o;
3964 }
3965
3966 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
3967
3968         $a = get_app();
3969
3970         if(! $item['parent'])
3971                 return;
3972
3973         if($item['deleted'])
3974                 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
3975
3976
3977         if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
3978                 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
3979         else
3980                 $body = $item['body'];
3981
3982         $o = "\r\n\r\n<entry>\r\n";
3983
3984         if(is_array($author))
3985                 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
3986         else
3987                 $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']));
3988         if(strlen($item['owner-name']))
3989                 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
3990
3991         if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
3992                 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
3993                 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' .  xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
3994         }
3995
3996         $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
3997         $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
3998         $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
3999         $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4000         $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4001         $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n";
4002         $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
4003         if($comment)
4004                 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4005
4006         if($item['location']) {
4007                 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4008                 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4009         }
4010
4011         if($item['coord'])
4012                 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4013
4014         if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4015                 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4016
4017         if($item['extid'])
4018                 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4019         if($item['bookmark'])
4020                 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4021
4022         if($item['app'])
4023                 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4024
4025         if($item['guid'])
4026                 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4027
4028         if($item['signed_text']) {
4029                 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4030                 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4031         }
4032
4033         $verb = construct_verb($item);
4034         $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4035         $actobj = construct_activity_object($item);
4036         if(strlen($actobj))
4037                 $o .= $actobj;
4038         $actarg = construct_activity_target($item);
4039         if(strlen($actarg))
4040                 $o .= $actarg;
4041
4042         $tags = item_getfeedtags($item);
4043         if(count($tags)) {
4044                 foreach($tags as $t) {
4045                         $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4046                 }
4047         }
4048
4049         $o .= item_getfeedattach($item);
4050
4051         $mentioned = get_mentions($item);
4052         if($mentioned)
4053                 $o .= $mentioned;
4054
4055         call_hooks('atom_entry', $o);
4056
4057         $o .= '</entry>' . "\r\n";
4058
4059         return $o;
4060 }
4061
4062 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4063
4064         if(get_config('system','disable_embedded'))
4065                 return $s;
4066
4067         $a = get_app();
4068
4069         logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4070         $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4071
4072         $orig_body = $s;
4073         $new_body = '';
4074
4075         $img_start = strpos($orig_body, '[img');
4076         $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4077         $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4078         while( ($img_st_close !== false) && ($img_len !== false) ) {
4079
4080                 $img_st_close++; // make it point to AFTER the closing bracket
4081                 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4082
4083                 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4084
4085
4086                 if(stristr($image , $site . '/photo/')) {
4087                         // Only embed locally hosted photos
4088                         $replace = false;
4089                         $i = basename($image);
4090                         $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4091                         $x = strpos($i,'-');
4092
4093                         if($x) {
4094                                 $res = substr($i,$x+1);
4095                                 $i = substr($i,0,$x);
4096                                 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4097                                         dbesc($i),
4098                                         intval($res),
4099                                         intval($uid)
4100                                 );
4101                                 if($r) {
4102
4103                                         // Check to see if we should replace this photo link with an embedded image
4104                                         // 1. No need to do so if the photo is public
4105                                         // 2. If there's a contact-id provided, see if they're in the access list
4106                                         //    for the photo. If so, embed it.
4107                                         // 3. Otherwise, if we have an item, see if the item permissions match the photo
4108                                         //    permissions, regardless of order but first check to see if they're an exact
4109                                         //    match to save some processing overhead.
4110
4111                                         if(has_permissions($r[0])) {
4112                                                 if($cid) {
4113                                                         $recips = enumerate_permissions($r[0]);
4114                                                         if(in_array($cid, $recips)) {
4115                                                                 $replace = true;
4116                                                         }
4117                                                 }
4118                                                 elseif($item) {
4119                                                         if(compare_permissions($item,$r[0]))
4120                                                                 $replace = true;
4121                                                 }
4122                                         }
4123                                         if($replace) {
4124                                                 $data = $r[0]['data'];
4125                                                 $type = $r[0]['type'];
4126
4127                                                 // If a custom width and height were specified, apply before embedding
4128                                                 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4129                                                         logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4130
4131                                                         $width = intval($match[1]);
4132                                                         $height = intval($match[2]);
4133
4134                                                         $ph = new Photo($data, $type);
4135                                                         if($ph->is_valid()) {
4136                                                                 $ph->scaleImage(max($width, $height));
4137                                                                 $data = $ph->imageString();
4138                                                                 $type = $ph->getType();
4139                                                         }
4140                                                 }
4141
4142                                                 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4143                                                 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4144                                                 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4145                                         }
4146                                 }
4147                         }
4148                 }
4149
4150                 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4151                 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4152                 if($orig_body === false)
4153                         $orig_body = '';
4154
4155                 $img_start = strpos($orig_body, '[img');
4156                 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4157                 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4158         }
4159
4160         $new_body = $new_body . $orig_body;
4161
4162         return($new_body);
4163 }
4164
4165
4166 function has_permissions($obj) {
4167         if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4168                 return true;
4169         return false;
4170 }
4171
4172 function compare_permissions($obj1,$obj2) {
4173         // first part is easy. Check that these are exactly the same.
4174         if(($obj1['allow_cid'] == $obj2['allow_cid'])
4175                 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4176                 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4177                 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4178                 return true;
4179
4180         // This is harder. Parse all the permissions and compare the resulting set.
4181
4182         $recipients1 = enumerate_permissions($obj1);
4183         $recipients2 = enumerate_permissions($obj2);
4184         sort($recipients1);
4185         sort($recipients2);
4186         if($recipients1 == $recipients2)
4187                 return true;
4188         return false;
4189 }
4190
4191 // returns an array of contact-ids that are allowed to see this object
4192
4193 function enumerate_permissions($obj) {
4194         require_once('include/group.php');
4195         $allow_people = expand_acl($obj['allow_cid']);
4196         $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4197         $deny_people  = expand_acl($obj['deny_cid']);
4198         $deny_groups  = expand_groups(expand_acl($obj['deny_gid']));
4199         $recipients   = array_unique(array_merge($allow_people,$allow_groups));
4200         $deny         = array_unique(array_merge($deny_people,$deny_groups));
4201         $recipients   = array_diff($recipients,$deny);
4202         return $recipients;
4203 }
4204
4205 function item_getfeedtags($item) {
4206         $ret = array();
4207         $matches = false;
4208         $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4209         if($cnt) {
4210                 for($x = 0; $x < $cnt; $x ++) {
4211                         if($matches[1][$x])
4212                                 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4213                 }
4214         }
4215         $matches = false;
4216         $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4217         if($cnt) {
4218                 for($x = 0; $x < $cnt; $x ++) {
4219                         if($matches[1][$x])
4220                                 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4221                 }
4222         }
4223         return $ret;
4224 }
4225
4226 function item_getfeedattach($item) {
4227         $ret = '';
4228         $arr = explode('[/attach],',$item['attach']);
4229         if(count($arr)) {
4230                 foreach($arr as $r) {
4231                         $matches = false;
4232                         $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4233                         if($cnt) {
4234                                 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4235                                 if(intval($matches[2]))
4236                                         $ret .= 'length="' . intval($matches[2]) . '" ';
4237                                 if($matches[4] !== ' ')
4238                                         $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4239                                 $ret .= ' />' . "\r\n";
4240                         }
4241                 }
4242         }
4243         return $ret;
4244 }
4245
4246
4247
4248 function item_expire($uid, $days, $network = "", $force = false) {
4249
4250         if((! $uid) || ($days < 1))
4251                 return;
4252
4253         // $expire_network_only = save your own wall posts
4254         // and just expire conversations started by others
4255
4256         $expire_network_only = get_pconfig($uid,'expire','network_only');
4257         $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4258
4259         if ($network != "") {
4260                 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4261                 // There is an index "uid_network_received" but not "uid_network_created"
4262                 // This avoids the creation of another index just for one purpose.
4263                 // And it doesn't really matter wether to look at "received" or "created"
4264                 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4265         } else
4266                 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4267
4268         $r = q("SELECT * FROM `item`
4269                 WHERE `uid` = %d $range
4270                 AND `id` = `parent`
4271                 $sql_extra
4272                 AND `deleted` = 0",
4273                 intval($uid),
4274                 intval($days)
4275         );
4276
4277         if(! count($r))
4278                 return;
4279
4280         $expire_items = get_pconfig($uid, 'expire','items');
4281         $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4282
4283         // Forcing expiring of items - but not notes and marked items
4284         if ($force)
4285                 $expire_items = true;
4286
4287         $expire_notes = get_pconfig($uid, 'expire','notes');
4288         $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4289
4290         $expire_starred = get_pconfig($uid, 'expire','starred');
4291         $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4292
4293         $expire_photos = get_pconfig($uid, 'expire','photos');
4294         $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4295
4296         logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4297
4298         foreach($r as $item) {
4299
4300                 // don't expire filed items
4301
4302                 if(strpos($item['file'],'[') !== false)
4303                         continue;
4304
4305                 // Only expire posts, not photos and photo comments
4306
4307                 if($expire_photos==0 && strlen($item['resource-id']))
4308                         continue;
4309                 if($expire_starred==0 && intval($item['starred']))
4310                         continue;
4311                 if($expire_notes==0 && $item['type']=='note')
4312                         continue;
4313                 if($expire_items==0 && $item['type']!='note')
4314                         continue;
4315
4316                 drop_item($item['id'],false);
4317         }
4318
4319         proc_run('php',"include/notifier.php","expire","$uid");
4320
4321 }
4322
4323
4324 function drop_items($items) {
4325         $uid = 0;
4326
4327         if(! local_user() && ! remote_user())
4328                 return;
4329
4330         if(count($items)) {
4331                 foreach($items as $item) {
4332                         $owner = drop_item($item,false);
4333                         if($owner && ! $uid)
4334                                 $uid = $owner;
4335                 }
4336         }
4337
4338         // multiple threads may have been deleted, send an expire notification
4339
4340         if($uid)
4341                 proc_run('php',"include/notifier.php","expire","$uid");
4342 }
4343
4344
4345 function drop_item($id,$interactive = true) {
4346
4347         $a = get_app();
4348
4349         // locate item to be deleted
4350
4351         $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4352                 intval($id)
4353         );
4354
4355         if(! count($r)) {
4356                 if(! $interactive)
4357                         return 0;
4358                 notice( t('Item not found.') . EOL);
4359                 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4360         }
4361
4362         $item = $r[0];
4363
4364         $owner = $item['uid'];
4365
4366         $cid = 0;
4367
4368         // check if logged in user is either the author or owner of this item
4369
4370         if(is_array($_SESSION['remote'])) {
4371                 foreach($_SESSION['remote'] as $visitor) {
4372                         if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4373                                 $cid = $visitor['cid'];
4374                                 break;
4375                         }
4376                 }
4377         }
4378
4379
4380         if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4381
4382                 // Check if we should do HTML-based delete confirmation
4383                 if($_REQUEST['confirm']) {
4384                         // <form> can't take arguments in its "action" parameter
4385                         // so add any arguments as hidden inputs
4386                         $query = explode_querystring($a->query_string);
4387                         $inputs = array();
4388                         foreach($query['args'] as $arg) {
4389                                 if(strpos($arg, 'confirm=') === false) {
4390                                         $arg_parts = explode('=', $arg);
4391                                         $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4392                                 }
4393                         }
4394
4395                         return replace_macros(get_markup_template('confirm.tpl'), array(
4396                                 '$method' => 'get',
4397                                 '$message' => t('Do you really want to delete this item?'),
4398                                 '$extra_inputs' => $inputs,
4399                                 '$confirm' => t('Yes'),
4400                                 '$confirm_url' => $query['base'],
4401                                 '$confirm_name' => 'confirmed',
4402                                 '$cancel' => t('Cancel'),
4403                         ));
4404                 }
4405                 // Now check how the user responded to the confirmation query
4406                 if($_REQUEST['canceled']) {
4407                         goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4408                 }
4409
4410                 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4411                 // delete the item
4412
4413                 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4414                         dbesc(datetime_convert()),
4415                         dbesc(datetime_convert()),
4416                         intval($item['id'])
4417                 );
4418                 create_tags_from_item($item['id']);
4419                 create_files_from_item($item['id']);
4420                 delete_thread($item['id']);
4421
4422                 // clean up categories and tags so they don't end up as orphans
4423
4424                 $matches = false;
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],true);
4429                         }
4430                 }
4431
4432                 $matches = false;
4433
4434                 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4435                 if($cnt) {
4436                         foreach($matches as $mtch) {
4437                                 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4438                         }
4439                 }
4440
4441                 // If item is a link to a photo resource, nuke all the associated photos
4442                 // (visitors will not have photo resources)
4443                 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4444                 // generate a resource-id and therefore aren't intimately linked to the item.
4445
4446                 if(strlen($item['resource-id'])) {
4447                         q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4448                                 dbesc($item['resource-id']),
4449                                 intval($item['uid'])
4450                         );
4451                         // ignore the result
4452                 }
4453
4454                 // If item is a link to an event, nuke the event record.
4455
4456                 if(intval($item['event-id'])) {
4457                         q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4458                                 intval($item['event-id']),
4459                                 intval($item['uid'])
4460                         );
4461                         // ignore the result
4462                 }
4463
4464                 // clean up item_id and sign meta-data tables
4465
4466                 /*
4467                 // Old code - caused very long queries and warning entries in the mysql logfiles:
4468
4469                 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4470                         intval($item['id']),
4471                         intval($item['uid'])
4472                 );
4473
4474                 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4475                         intval($item['id']),
4476                         intval($item['uid'])
4477                 );
4478                 */
4479
4480                 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4481
4482                 // Creating list of parents
4483                 $r = q("select id from item where parent = %d and uid = %d",
4484                         intval($item['id']),
4485                         intval($item['uid'])
4486                 );
4487
4488                 $parentid = "";
4489
4490                 foreach ($r AS $row) {
4491                         if ($parentid != "")
4492                                 $parentid .= ", ";
4493
4494                         $parentid .= $row["id"];
4495                 }
4496
4497                 // Now delete them
4498                 if ($parentid != "") {
4499                         $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4500
4501                         $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4502                 }
4503
4504                 // If it's the parent of a comment thread, kill all the kids
4505
4506                 if($item['uri'] == $item['parent-uri']) {
4507                         $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4508                                 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4509                                 dbesc(datetime_convert()),
4510                                 dbesc(datetime_convert()),
4511                                 dbesc($item['parent-uri']),
4512                                 intval($item['uid'])
4513                         );
4514                         create_tags_from_item($item['parent-uri'], $item['uid']);
4515                         create_files_from_item($item['parent-uri'], $item['uid']);
4516                         delete_thread_uri($item['parent-uri'], $item['uid']);
4517                         // ignore the result
4518                 }
4519                 else {
4520                         // ensure that last-child is set in case the comment that had it just got wiped.
4521                         q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4522                                 dbesc(datetime_convert()),
4523                                 dbesc($item['parent-uri']),
4524                                 intval($item['uid'])
4525                         );
4526                         // who is the last child now?
4527                         $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",
4528                                 dbesc($item['parent-uri']),
4529                                 intval($item['uid'])
4530                         );
4531                         if(count($r)) {
4532                                 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4533                                         intval($r[0]['id'])
4534                                 );
4535                         }
4536
4537                         // Add a relayable_retraction signature for Diaspora.
4538                         store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4539                 }
4540                 $drop_id = intval($item['id']);
4541
4542                 // send the notification upstream/downstream as the case may be
4543
4544                 proc_run('php',"include/notifier.php","drop","$drop_id");
4545
4546                 if(! $interactive)
4547                         return $owner;
4548                 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4549                 //NOTREACHED
4550         }
4551         else {
4552                 if(! $interactive)
4553                         return 0;
4554                 notice( t('Permission denied.') . EOL);
4555                 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4556                 //NOTREACHED
4557         }
4558
4559 }
4560
4561
4562 function first_post_date($uid,$wall = false) {
4563         $r = q("select id, created from item
4564                 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4565                 and id = parent
4566                 order by created asc limit 1",
4567                 intval($uid),
4568                 intval($wall ? 1 : 0)
4569         );
4570         if(count($r)) {
4571 //              logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4572                 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4573         }
4574         return false;
4575 }
4576
4577 function posted_dates($uid,$wall) {
4578         $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4579
4580         $dthen = first_post_date($uid,$wall);
4581         if(! $dthen)
4582                 return array();
4583
4584         // Set the start and end date to the beginning of the month
4585         $dnow = substr($dnow,0,8).'01';
4586         $dthen = substr($dthen,0,8).'01';
4587
4588         $ret = array();
4589         // Starting with the current month, get the first and last days of every
4590         // month down to and including the month of the first post
4591         while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4592                 $dstart = substr($dnow,0,8) . '01';
4593                 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4594                 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4595                 $end_month = datetime_convert('','',$dend,'Y-m-d');
4596                 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4597                 $ret[] = array($str,$end_month,$start_month);
4598                 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4599         }
4600         return $ret;
4601 }
4602
4603
4604 function posted_date_widget($url,$uid,$wall) {
4605         $o = '';
4606
4607         if(! feature_enabled($uid,'archives'))
4608                 return $o;
4609
4610         // For former Facebook folks that left because of "timeline"
4611
4612 /*      if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4613                 return $o;*/
4614
4615         $ret = posted_dates($uid,$wall);
4616         if(! count($ret))
4617                 return $o;
4618
4619         $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4620                 '$title' => t('Archives'),
4621                 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4622                 '$url' => $url,
4623                 '$dates' => $ret
4624         ));
4625         return $o;
4626 }
4627
4628 function store_diaspora_retract_sig($item, $user, $baseurl) {
4629         // Note that we can't add a target_author_signature
4630         // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4631         // the comment, that means we're the home of the post, and Diaspora will only
4632         // check the parent_author_signature of retractions that it doesn't have to relay further
4633         //
4634         // I don't think this function gets called for an "unlike," but I'll check anyway
4635
4636         $enabled = intval(get_config('system','diaspora_enabled'));
4637         if(! $enabled) {
4638                 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4639                 return;
4640         }
4641
4642         logger('drop_item: storing diaspora retraction signature');
4643
4644         $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4645
4646         if(local_user() == $item['uid']) {
4647
4648                 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4649                 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4650         }
4651         else {
4652                 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4653                         $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4654                 );
4655                 if(count($r)) {
4656                         // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4657                         // only handles DFRN deletes
4658                         $handle_baseurl_start = strpos($r['url'],'://') + 3;
4659                         $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4660                         $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4661                         $authorsig = '';
4662                 }
4663         }
4664
4665         if(isset($handle))
4666                 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4667                         intval($item['id']),
4668                         dbesc($signed_text),
4669                         dbesc($authorsig),
4670                         dbesc($handle)
4671                 );
4672
4673         return;
4674 }