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