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