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