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