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