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