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