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