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