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