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