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