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