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