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