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