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