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