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