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