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