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