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