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