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/threads.php');
13 require_once('include/socgraph.php');
14 require_once('include/plaintext.php');
15 require_once('include/ostatus.php');
16 require_once('mod/share.php');
18 require_once('library/defuse/php-encryption-1.2.1/Crypto.php');
21 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0, $forpubsub = false) {
24 $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
25 $public_feed = (($dfrn_id) ? false : true);
26 $starred = false; // not yet implemented, possible security issues
29 if($public_feed && $a->argc > 2) {
30 for($x = 2; $x < $a->argc; $x++) {
31 if($a->argv[$x] == 'converse')
33 if($a->argv[$x] == 'starred')
35 if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
36 $category = $a->argv[$x+1];
42 // default permissions - anonymous user
44 $sql_extra = " AND `allow_cid` = '' AND `allow_gid` = '' AND `deny_cid` = '' AND `deny_gid` = '' ";
46 $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
47 FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
48 WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
56 $owner_id = $owner['user_uid'];
57 $owner_nick = $owner['nickname'];
59 $birthday = feed_birthday($owner_id,$owner['timezone']);
69 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
73 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
74 $my_id = '1:' . $dfrn_id;
77 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
78 $my_id = '0:' . $dfrn_id;
85 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
93 require_once('include/security.php');
94 $groups = init_groups_visitor($contact['id']);
97 for($x = 0; $x < count($groups); $x ++)
98 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
99 $gs = implode('|', $groups);
102 $gs = '<<>>' ; // Impossible to match
104 $sql_extra = sprintf("
105 AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
106 AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
107 AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
108 AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
110 intval($contact['id']),
111 intval($contact['id']),
122 // Include answers to status.net posts in pubsub feeds
124 $sql_post_table = "INNER JOIN `thread` ON `thread`.`iid` = `item`.`parent` ";
125 $visibility = sprintf("AND (`item`.`parent` = `item`.`id`) OR (`item`.`network` = '%s' AND `thread`.`network`='%s')",
126 dbesc(NETWORK_DFRN), dbesc(NETWORK_OSTATUS));
127 $date_field = "`received`";
128 $sql_order = "`item`.`received` DESC";
130 $date_field = "`changed`";
131 $sql_order = "`item`.`parent` ".$sort.", `item`.`created` ASC";
134 if(! strlen($last_update))
135 $last_update = 'now -30 days';
137 if(isset($category)) {
138 $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` ",
139 dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
140 //$sql_extra .= file_tag_file_query('item',$category,'category');
145 $sql_extra .= " AND `contact`.`self` = 1 ";
148 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
150 // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
151 // dbesc($check_date),
153 $r = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id`,
154 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
155 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
156 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
157 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
158 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
159 FROM `item` $sql_post_table
160 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
161 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
162 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
163 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
164 AND ((`item`.`wall` = 1) $visibility) AND `item`.$date_field > '%s'
166 ORDER BY $sql_order LIMIT 0, 300",
172 // Will check further below if this actually returned results.
173 // We will provide an empty feed if that is the case.
177 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
181 $hubxml = feed_hublinks();
183 $salmon = feed_salmonlinks($owner_nick);
185 $alternatelink = $owner['url'];
188 $alternatelink .= "/category/".$category;
190 $atom .= replace_macros($feed_template, array(
191 '$version' => xmlify(FRIENDICA_VERSION),
192 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
193 '$feed_title' => xmlify($owner['name']),
194 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
196 '$salmon' => $salmon,
197 '$alternatelink' => xmlify($alternatelink),
198 '$name' => xmlify($owner['name']),
199 '$profile_page' => xmlify($owner['url']),
200 '$photo' => xmlify($owner['photo']),
201 '$thumb' => xmlify($owner['thumb']),
202 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
203 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
204 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
205 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
206 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
209 call_hooks('atom_feed', $atom);
211 if(! count($items)) {
213 call_hooks('atom_feed_end', $atom);
215 $atom .= '</feed>' . "\r\n";
219 foreach($items as $item) {
221 // prevent private email from leaking.
222 if($item['network'] === NETWORK_MAIL)
225 // public feeds get html, our own nodes use bbcode
229 // catch any email that's in a public conversation and make sure it doesn't leak
237 $atom .= atom_entry($item,$type,null,$owner,true);
240 call_hooks('atom_feed_end', $atom);
242 $atom .= '</feed>' . "\r\n";
248 function construct_verb($item) {
250 return $item['verb'];
251 return ACTIVITY_POST;
254 function construct_activity_object($item) {
256 if($item['object']) {
257 $o = '<as:object>' . "\r\n";
258 $r = parse_xml_string($item['object'],false);
264 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
266 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
268 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
270 if(substr($r->link,0,1) === '<') {
271 // patch up some facebook "like" activity objects that got stored incorrectly
272 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
273 // we can probably remove this hack here and in the following function in a few months time.
274 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
275 $r->link = str_replace('&','&', $r->link);
276 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
280 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
283 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
284 $o .= '</as:object>' . "\r\n";
291 function construct_activity_target($item) {
293 if($item['target']) {
294 $o = '<as:target>' . "\r\n";
295 $r = parse_xml_string($item['target'],false);
299 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
301 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
303 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
305 if(substr($r->link,0,1) === '<') {
306 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
307 $r->link = str_replace('&','&', $r->link);
308 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
312 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
315 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
316 $o .= '</as:target>' . "\r\n";
325 * The purpose of this function is to apply system message length limits to
326 * imported messages without including any embedded photos in the length
328 if(! function_exists('limit_body_size')) {
329 function limit_body_size($body) {
331 // logger('limit_body_size: start', LOGGER_DEBUG);
333 $maxlen = get_max_import_size();
335 // If the length of the body, including the embedded images, is smaller
336 // than the maximum, then don't waste time looking for the images
337 if($maxlen && (strlen($body) > $maxlen)) {
339 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
346 $img_start = strpos($orig_body, '[img');
347 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
348 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
349 while(($img_st_close !== false) && ($img_end !== false)) {
351 $img_st_close++; // make it point to AFTER the closing bracket
352 $img_end += $img_start;
353 $img_end += strlen('[/img]');
355 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
356 // This is an embedded image
358 if( ($textlen + $img_start) > $maxlen ) {
359 if($textlen < $maxlen) {
360 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
361 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
366 $new_body = $new_body . substr($orig_body, 0, $img_start);
367 $textlen += $img_start;
370 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
374 if( ($textlen + $img_end) > $maxlen ) {
375 if($textlen < $maxlen) {
376 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
377 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
382 $new_body = $new_body . substr($orig_body, 0, $img_end);
383 $textlen += $img_end;
386 $orig_body = substr($orig_body, $img_end);
388 if($orig_body === false) // in case the body ends on a closing image tag
391 $img_start = strpos($orig_body, '[img');
392 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
393 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
396 if( ($textlen + strlen($orig_body)) > $maxlen) {
397 if($textlen < $maxlen) {
398 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
399 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
404 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
405 $new_body = $new_body . $orig_body;
406 $textlen += strlen($orig_body);
415 function title_is_body($title, $body) {
417 $title = strip_tags($title);
418 $title = trim($title);
419 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
420 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
422 $body = strip_tags($body);
424 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
425 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
427 if (strlen($title) < strlen($body))
428 $body = substr($body, 0, strlen($title));
430 if (($title != $body) and (substr($title, -3) == "...")) {
431 $pos = strrpos($title, "...");
433 $title = substr($title, 0, $pos);
434 $body = substr($body, 0, $pos);
438 return($title == $body);
443 function get_atom_elements($feed, $item, $contact = array()) {
445 require_once('library/HTMLPurifier.auto.php');
446 require_once('include/html2bbcode.php');
448 $best_photo = array();
452 $author = $item->get_author();
454 $res['author-name'] = unxmlify($author->get_name());
455 $res['author-link'] = unxmlify($author->get_link());
458 $res['author-name'] = unxmlify($feed->get_title());
459 $res['author-link'] = unxmlify($feed->get_permalink());
461 $res['uri'] = unxmlify($item->get_id());
462 $res['title'] = unxmlify($item->get_title());
463 $res['body'] = unxmlify($item->get_content());
464 $res['plink'] = unxmlify($item->get_link(0));
466 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
467 logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
469 $res['body'] = nl2br($res['body']);
472 // removing the content of the title if its identically to the body
473 // This helps with auto generated titles e.g. from tumblr
474 if (title_is_body($res["title"], $res["body"]))
478 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
482 // look for a photo. We should check media size and find the best one,
483 // but for now let's just find any author photo
484 // Additionally we look for an alternate author link. On OStatus this one is the one we want.
486 $authorlinks = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"]["http://www.w3.org/2005/Atom"]["link"];
487 if (is_array($authorlinks)) {
488 foreach ($authorlinks as $link) {
489 $linkdata = array_shift($link["attribs"]);
491 if ($linkdata["rel"] == "alternate")
492 $res["author-link"] = $linkdata["href"];
496 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
498 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
499 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
500 foreach($base as $link) {
501 if($link['attribs']['']['rel'] === 'alternate')
502 $res['author-link'] = unxmlify($link['attribs']['']['href']);
504 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
505 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
506 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
511 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
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 if($base && count($base)) {
516 foreach($base as $link) {
517 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
518 $res['author-link'] = unxmlify($link['attribs']['']['href']);
519 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
520 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
521 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
527 // No photo/profile-link on the item - look at the feed level
529 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
530 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
531 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
532 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
533 foreach($base as $link) {
534 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
535 $res['author-link'] = unxmlify($link['attribs']['']['href']);
536 if(! $res['author-avatar']) {
537 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
538 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
543 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
545 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
546 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
548 if($base && count($base)) {
549 foreach($base as $link) {
550 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
551 $res['author-link'] = unxmlify($link['attribs']['']['href']);
552 if(! (x($res,'author-avatar'))) {
553 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
554 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
561 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
562 if($apps && $apps[0]['attribs']['']['source']) {
563 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
564 if($res['app'] === 'web')
565 $res['app'] = 'OStatus';
568 // base64 encoded json structure representing Diaspora signature
570 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
572 $res['dsprsig'] = unxmlify($dsig[0]['data']);
575 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
577 $res['guid'] = unxmlify($dguid[0]['data']);
579 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
581 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
585 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
588 $have_real_body = false;
590 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
592 $have_real_body = true;
593 $res['body'] = $rawenv[0]['data'];
594 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
595 // make sure nobody is trying to sneak some html tags by us
596 $res['body'] = notags(base64url_decode($res['body']));
600 $res['body'] = limit_body_size($res['body']);
602 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
603 // the content type. Our own network only emits text normally, though it might have been converted to
604 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
605 // have to assume it is all html and needs to be purified.
607 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
608 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
609 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
612 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
614 $res['body'] = reltoabs($res['body'],$base_url);
616 $res['body'] = html2bb_video($res['body']);
618 $res['body'] = oembed_html2bbcode($res['body']);
620 $config = HTMLPurifier_Config::createDefault();
621 $config->set('Cache.DefinitionImpl', null);
623 // we shouldn't need a whitelist, because the bbcode converter
624 // will strip out any unsupported tags.
626 $purifier = new HTMLPurifier($config);
627 $res['body'] = $purifier->purify($res['body']);
629 $res['body'] = @html2bbcode($res['body']);
633 elseif(! $have_real_body) {
635 // it's not one of our messages and it has no tags
636 // so it's probably just text. We'll escape it just to be safe.
638 $res['body'] = escape_tags($res['body']);
642 // this tag is obsolete but we keep it for really old sites
644 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
645 if($allow && $allow[0]['data'] == 1)
646 $res['last-child'] = 1;
648 $res['last-child'] = 0;
650 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
651 if($private && intval($private[0]['data']) > 0)
652 $res['private'] = intval($private[0]['data']);
656 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
657 if($extid && $extid[0]['data'])
658 $res['extid'] = $extid[0]['data'];
660 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
662 $res['location'] = unxmlify($rawlocation[0]['data']);
665 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
667 $res['created'] = unxmlify($rawcreated[0]['data']);
670 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
672 $res['edited'] = unxmlify($rawedited[0]['data']);
674 if((x($res,'edited')) && (! (x($res,'created'))))
675 $res['created'] = $res['edited'];
677 if(! $res['created'])
678 $res['created'] = $item->get_date('c');
681 $res['edited'] = $item->get_date('c');
684 // Disallow time travelling posts
686 $d1 = strtotime($res['created']);
687 $d2 = strtotime($res['edited']);
688 $d3 = strtotime('now');
691 $res['created'] = datetime_convert();
693 $res['edited'] = datetime_convert();
695 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
696 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
697 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
698 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
699 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
700 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
701 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
702 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
703 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
705 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
706 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
708 foreach($base as $link) {
709 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
710 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
711 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
716 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
718 $res['coord'] = unxmlify($rawgeo[0]['data']);
720 if ($contact["network"] == NETWORK_FEED) {
721 $res['verb'] = ACTIVITY_POST;
722 $res['object-type'] = ACTIVITY_OBJ_NOTE;
725 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
727 // select between supported verbs
730 $res['verb'] = unxmlify($rawverb[0]['data']);
733 // translate OStatus unfollow to activity streams if it happened to get selected
735 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
736 $res['verb'] = ACTIVITY_UNFOLLOW;
738 $cats = $item->get_categories();
741 foreach($cats as $cat) {
742 $term = $cat->get_term();
744 $term = $cat->get_label();
745 $scheme = $cat->get_scheme();
746 if($scheme && $term && stristr($scheme,'X-DFRN:'))
747 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
749 $tag_arr[] = notags(trim($term));
751 $res['tag'] = implode(',', $tag_arr);
754 $attach = $item->get_enclosures();
757 foreach($attach as $att) {
758 $len = intval($att->get_length());
759 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
760 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
761 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
762 if(strpos($type,';'))
763 $type = substr($type,0,strpos($type,';'));
764 if((! $link) || (strpos($link,'http') !== 0))
770 $type = 'application/octet-stream';
772 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
774 $res['attach'] = implode(',', $att_arr);
777 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
780 $res['object'] = '<object>' . "\n";
781 $child = $rawobj[0]['child'];
782 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
783 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
784 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
786 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
787 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
788 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
789 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
790 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
791 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
792 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
793 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
795 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
796 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
797 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
798 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
800 $body = html2bb_video($body);
802 $config = HTMLPurifier_Config::createDefault();
803 $config->set('Cache.DefinitionImpl', null);
805 $purifier = new HTMLPurifier($config);
806 $body = $purifier->purify($body);
807 $body = html2bbcode($body);
810 $res['object'] .= '<content>' . $body . '</content>' . "\n";
813 $res['object'] .= '</object>' . "\n";
816 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
819 $res['target'] = '<target>' . "\n";
820 $child = $rawobj[0]['child'];
821 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
822 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
824 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
825 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
826 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
827 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
828 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
829 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
830 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
831 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
833 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
834 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
835 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
836 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
838 $body = html2bb_video($body);
840 $config = HTMLPurifier_Config::createDefault();
841 $config->set('Cache.DefinitionImpl', null);
843 $purifier = new HTMLPurifier($config);
844 $body = $purifier->purify($body);
845 $body = html2bbcode($body);
848 $res['target'] .= '<content>' . $body . '</content>' . "\n";
851 $res['target'] .= '</target>' . "\n";
854 // This is some experimental stuff. By now retweets are shown with "RT:"
855 // But: There is data so that the message could be shown similar to native retweets
856 // There is some better way to parse this array - but it didn't worked for me.
857 $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"];
858 if (is_array($child)) {
859 logger('get_atom_elements: Looking for status.net repeated message');
861 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
862 $orig_id = ostatus_convert_href($child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"]);
863 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
864 $uri = $author["uri"][0]["data"];
865 $name = $author["name"][0]["data"];
866 $avatar = @array_shift($author["link"][2]["attribs"]);
867 $avatar = $avatar["href"];
869 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
870 logger('get_atom_elements: fixing sender of repeated message. '.$orig_id, LOGGER_DEBUG);
872 if (!intval(get_config('system','wall-to-wall_share'))) {
873 $prefix = share_header($name, $uri, $avatar, "", "", $orig_link);
875 $res["body"] = $prefix.html2bbcode($message)."[/share]";
877 $res["owner-name"] = $res["author-name"];
878 $res["owner-link"] = $res["author-link"];
879 $res["owner-avatar"] = $res["author-avatar"];
881 $res["author-name"] = $name;
882 $res["author-link"] = $uri;
883 $res["author-avatar"] = $avatar;
885 $res["body"] = html2bbcode($message);
890 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
893 // Handle enclosures and treat them as preview picture
895 foreach ($attach AS $attachment)
896 if ($attachment->type == "image/jpeg")
897 $preview = $attachment->link;
899 $res["body"] = $res["title"].add_page_info($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
900 $res["tag"] = add_page_keywords($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
902 $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
903 unset($res["attach"]);
904 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
905 $res["body"] = add_page_info_to_body($res["body"]);
906 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
907 $res["body"] = add_page_info_to_body($res["body"]);
910 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
912 call_hooks('parse_atom', $arr);
917 function add_page_info_data($data) {
918 call_hooks('page_info_data', $data);
920 // It maybe is a rich content, but if it does have everything that a link has,
921 // then treat it that way
922 if (($data["type"] == "rich") AND is_string($data["title"]) AND
923 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
924 $data["type"] = "link";
926 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
929 if ($no_photos AND ($data["type"] == "photo"))
932 // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
933 if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
934 require_once("include/network.php");
935 $data["url"] = short_link($data["url"]);
938 if (($data["type"] != "photo") AND is_string($data["title"]))
939 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
941 if (($data["type"] != "video") AND ($photo != ""))
942 $text .= '[img]'.$photo.'[/img]';
943 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
944 $imagedata = $data["images"][0];
945 $text .= '[img]'.$imagedata["src"].'[/img]';
948 if (($data["type"] != "photo") AND is_string($data["text"]))
949 $text .= "[quote]".$data["text"]."[/quote]";
952 if (isset($data["keywords"]) AND count($data["keywords"])) {
955 foreach ($data["keywords"] AS $keyword) {
956 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
957 array("","", "", "", "", ""), $keyword);
958 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
962 return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
965 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
966 require_once("mod/parse_url.php");
968 $data = Cache::get("parse_url:".$url);
970 $data = parseurl_getsiteinfo($url, true);
971 Cache::set("parse_url:".$url,serialize($data));
973 $data = unserialize($data);
976 $data["images"][0]["src"] = $photo;
978 logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
980 if (!$keywords AND isset($data["keywords"]))
981 unset($data["keywords"]);
983 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
984 $list = explode(",", $keyword_blacklist);
985 foreach ($list AS $keyword) {
986 $keyword = trim($keyword);
987 $index = array_search($keyword, $data["keywords"]);
988 if ($index !== false)
989 unset($data["keywords"][$index]);
996 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
997 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1000 if (isset($data["keywords"]) AND count($data["keywords"])) {
1002 foreach ($data["keywords"] AS $keyword) {
1003 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
1004 array("","", "", "", "", ""), $keyword);
1009 $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
1016 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
1017 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1019 $text = add_page_info_data($data);
1024 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
1026 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
1028 $URLSearchString = "^\[\]";
1030 // Adding these spaces is a quick hack due to my problems with regular expressions :)
1031 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
1034 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
1036 // Convert urls without bbcode elements
1037 if (!$matches AND $texturl) {
1038 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
1040 // Yeah, a hack. I really hate regular expressions :)
1042 $matches[1] = $matches[2];
1046 $footer = add_page_info($matches[1], $no_photos);
1048 // Remove the link from the body if the link is attached at the end of the post
1049 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
1050 $removedlink = trim(str_replace($matches[1], "", $body));
1051 if (($removedlink == "") OR strstr($body, $removedlink))
1052 $body = $removedlink;
1054 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
1055 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
1056 if (($removedlink == "") OR strstr($body, $removedlink))
1057 $body = $removedlink;
1060 // Add the page information to the bottom
1061 if (isset($footer) AND (trim($footer) != ""))
1067 function encode_rel_links($links) {
1069 if(! ((is_array($links)) && (count($links))))
1071 foreach($links as $link) {
1073 if($link['attribs']['']['rel'])
1074 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
1075 if($link['attribs']['']['type'])
1076 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
1077 if($link['attribs']['']['href'])
1078 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
1079 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
1080 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
1081 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
1082 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
1083 $o .= ' />' . "\n" ;
1088 function add_guid($item) {
1089 $r = q("SELECT `guid` FROM `guid` WHERE `guid` = '%s' LIMIT 1", dbesc($item["guid"]));
1093 q("INSERT INTO `guid` (`guid`,`plink`,`uri`,`network`) VALUES ('%s','%s','%s','%s')",
1094 dbesc($item["guid"]), dbesc($item["plink"]),
1095 dbesc($item["uri"]), dbesc($item["network"]));
1098 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
1100 // If it is a posting where users should get notifications, then define it as wall posting
1103 $arr['type'] = 'wall';
1105 $arr['last-child'] = 1;
1106 $arr['network'] = NETWORK_DFRN;
1109 // If a Diaspora signature structure was passed in, pull it out of the
1110 // item array and set it aside for later storage.
1113 if(x($arr,'dsprsig')) {
1114 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1115 unset($arr['dsprsig']);
1118 // Converting the plink
1119 if ($arr['network'] == NETWORK_OSTATUS) {
1120 if (isset($arr['plink']))
1121 $arr['plink'] = ostatus_convert_href($arr['plink']);
1122 elseif (isset($arr['uri']))
1123 $arr['plink'] = ostatus_convert_href($arr['uri']);
1126 if(x($arr, 'gravity'))
1127 $arr['gravity'] = intval($arr['gravity']);
1128 elseif($arr['parent-uri'] === $arr['uri'])
1129 $arr['gravity'] = 0;
1130 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1131 $arr['gravity'] = 6;
1133 $arr['gravity'] = 6; // extensible catchall
1135 if(! x($arr,'type'))
1136 $arr['type'] = 'remote';
1140 /* check for create date and expire time */
1141 $uid = intval($arr['uid']);
1142 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
1144 $expire_interval = $r[0]['expire'];
1145 if ($expire_interval>0) {
1146 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1147 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1148 if ($created_date < $expire_date) {
1149 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1155 // If there is no guid then take the same guid that was taken before for the same uri
1156 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "") AND (trim($arr['network']) != "")) {
1157 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1158 $r = q("SELECT `guid` FROM `guid` WHERE `uri` = '%s' AND `network` = '%s' LIMIT 1",
1159 dbesc(trim($arr['uri'])), dbesc(trim($arr['network'])));
1162 $arr['guid'] = $r[0]["guid"];
1163 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1167 // If there is no guid then take the same guid that was taken before for the same plink
1168 if ((trim($arr['guid']) == "") AND (trim($arr['plink']) != "") AND (trim($arr['network']) != "")) {
1169 logger('item_store: checking for an existing guid for plink '.$arr['plink'], LOGGER_DEBUG);
1170 $r = q("SELECT `guid`, `uri` FROM `guid` WHERE `plink` = '%s' AND `network` = '%s' LIMIT 1",
1171 dbesc(trim($arr['plink'])), dbesc(trim($arr['network'])));
1174 $arr['guid'] = $r[0]["guid"];
1175 logger('item_store: found guid '.$arr['guid'].' for plink '.$arr['plink'], LOGGER_DEBUG);
1177 if ($r[0]["uri"] != $arr['uri'])
1178 logger('Different uri for same guid: '.$arr['uri'].' and '.$r[0]["uri"].' - this shouldnt happen!', LOGGER_DEBUG);
1182 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1183 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1184 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1185 // $arr['body'] = strip_tags($arr['body']);
1188 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1189 require_once('library/langdet/Text/LanguageDetect.php');
1190 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1191 $l = new Text_LanguageDetect;
1192 //$lng = $l->detectConfidence($naked_body);
1193 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1194 $lng = $l->detect($naked_body, 3);
1196 if (sizeof($lng) > 0) {
1199 foreach ($lng as $language => $score) {
1200 if ($postopts == "")
1201 $postopts = "lang=";
1205 $postopts .= $language.";".$score;
1207 $arr['postopts'] = $postopts;
1211 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1212 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1213 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1214 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1215 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1216 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1217 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1218 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1219 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1220 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1221 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1222 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
1223 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
1224 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
1225 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1226 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1227 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1228 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1229 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1230 $arr['deleted'] = 0;
1231 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1232 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1233 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1234 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1235 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1236 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1237 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1238 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1239 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1240 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1241 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1242 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1243 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1244 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1245 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1246 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1247 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1248 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1249 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1250 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $arr['network']));
1251 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
1252 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
1253 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
1254 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
1255 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
1257 if ($arr['plink'] == "") {
1259 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1262 if ($arr['network'] == "") {
1263 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1264 intval($arr['contact-id']),
1269 $arr['network'] = $r[0]["network"];
1271 // Fallback to friendica (why is it empty in some cases?)
1272 if ($arr['network'] == "")
1273 $arr['network'] = NETWORK_DFRN;
1275 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1278 if ($arr['guid'] != "") {
1279 // Checking if there is already an item with the same guid
1280 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1281 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1282 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1285 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1290 // Check for hashtags in the body and repair or add hashtag links
1291 item_body_set_hashtags($arr);
1293 $arr['thr-parent'] = $arr['parent-uri'];
1294 if($arr['parent-uri'] === $arr['uri']) {
1296 $parent_deleted = 0;
1297 $allow_cid = $arr['allow_cid'];
1298 $allow_gid = $arr['allow_gid'];
1299 $deny_cid = $arr['deny_cid'];
1300 $deny_gid = $arr['deny_gid'];
1301 $notify_type = 'wall-new';
1305 // find the parent and snarf the item id and ACLs
1306 // and anything else we need to inherit
1308 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1309 dbesc($arr['parent-uri']),
1315 // is the new message multi-level threaded?
1316 // even though we don't support it now, preserve the info
1317 // and re-attach to the conversation parent.
1319 if($r[0]['uri'] != $r[0]['parent-uri']) {
1320 $arr['parent-uri'] = $r[0]['parent-uri'];
1321 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1322 ORDER BY `id` ASC LIMIT 1",
1323 dbesc($r[0]['parent-uri']),
1324 dbesc($r[0]['parent-uri']),
1331 $parent_id = $r[0]['id'];
1332 $parent_deleted = $r[0]['deleted'];
1333 $allow_cid = $r[0]['allow_cid'];
1334 $allow_gid = $r[0]['allow_gid'];
1335 $deny_cid = $r[0]['deny_cid'];
1336 $deny_gid = $r[0]['deny_gid'];
1337 $arr['wall'] = $r[0]['wall'];
1338 $notify_type = 'comment-new';
1340 // if the parent is private, force privacy for the entire conversation
1341 // This differs from the above settings as it subtly allows comments from
1342 // email correspondents to be private even if the overall thread is not.
1344 if($r[0]['private'])
1345 $arr['private'] = $r[0]['private'];
1347 // Edge case. We host a public forum that was originally posted to privately.
1348 // The original author commented, but as this is a comment, the permissions
1349 // weren't fixed up so it will still show the comment as private unless we fix it here.
1351 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1352 $arr['private'] = 0;
1355 // If its a post from myself then tag the thread as "mention"
1356 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1357 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1360 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1361 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1362 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1363 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1364 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1370 // Allow one to see reply tweets from status.net even when
1371 // we don't have or can't see the original post.
1374 logger('item_store: $force_parent=true, reply converted to top-level post.');
1376 $arr['parent-uri'] = $arr['uri'];
1377 $arr['gravity'] = 0;
1380 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
1384 $parent_deleted = 0;
1388 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` = '%s' AND `uid` = %d LIMIT 1",
1390 dbesc($arr['network']),
1393 if($r && count($r)) {
1394 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1398 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1399 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1400 dbesc($arr['body']),
1401 dbesc($arr['network']),
1402 dbesc($arr['created']),
1403 intval($arr['contact-id']),
1406 if($r && count($r)) {
1407 logger('duplicated item with the same body found. ' . print_r($arr,true));
1411 // Is this item available in the global items (with uid=0)?
1412 if ($arr["uid"] == 0) {
1413 $arr["global"] = true;
1415 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1417 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1419 $arr["global"] = (count($isglobal) > 0);
1422 // Fill the cache field
1423 put_item_in_cache($arr);
1425 call_hooks('post_remote',$arr);
1427 if(x($arr,'cancel')) {
1428 logger('item_store: post cancelled by plugin.');
1432 // Store the unescaped version
1437 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1439 $r = dbq("INSERT INTO `item` (`"
1440 . implode("`, `", array_keys($arr))
1442 . implode("', '", array_values($arr))
1448 // find the item we just created
1449 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1456 // Store the guid and other relevant data
1459 $current_post = $r[0]['id'];
1460 logger('item_store: created item ' . $current_post);
1462 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1463 // This can be used to filter for inactive contacts.
1464 // Only do this for public postings to avoid privacy problems, since poco data is public.
1465 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1467 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1469 // Is it a forum? Then we don't care about the rules from above
1470 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1471 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1472 intval($arr['contact-id']));
1478 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1479 dbesc($arr['received']),
1480 dbesc($arr['received']),
1481 intval($arr['contact-id'])
1484 logger('item_store: could not locate created item');
1488 logger('item_store: duplicated post occurred. Removing duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1489 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1491 intval($arr['uid']),
1492 intval($current_post)
1496 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1497 $parent_id = $current_post;
1499 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1502 $private = $arr['private'];
1504 // Set parent id - and also make sure to inherit the parent's ACLs.
1506 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1507 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1514 intval($parent_deleted),
1515 intval($current_post)
1518 $arr['id'] = $current_post;
1519 $arr['parent'] = $parent_id;
1520 $arr['allow_cid'] = $allow_cid;
1521 $arr['allow_gid'] = $allow_gid;
1522 $arr['deny_cid'] = $deny_cid;
1523 $arr['deny_gid'] = $deny_gid;
1524 $arr['private'] = $private;
1525 $arr['deleted'] = $parent_deleted;
1527 // update the commented timestamp on the parent
1528 // Only update "commented" if it is really a comment
1529 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1530 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1531 dbesc(datetime_convert()),
1532 dbesc(datetime_convert()),
1536 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1537 dbesc(datetime_convert()),
1542 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1543 intval($current_post),
1544 dbesc($dsprsig->signed_text),
1545 dbesc($dsprsig->signature),
1546 dbesc($dsprsig->signer)
1552 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1555 if($arr['last-child']) {
1556 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1558 intval($arr['uid']),
1559 intval($current_post)
1563 $deleted = tag_deliver($arr['uid'],$current_post);
1565 // current post can be deleted if is for a community page and no mention are
1567 if (!$deleted AND !$dontcache) {
1569 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1570 if (count($r) == 1) {
1571 call_hooks('post_remote_end', $r[0]);
1573 logger('item_store: new item not found in DB, id ' . $current_post);
1576 // Add every contact of the post to the global contact table
1579 create_tags_from_item($current_post);
1580 create_files_from_item($current_post);
1582 // Only check for notifications on start posts
1583 if ($arr['parent-uri'] === $arr['uri']) {
1584 add_thread($current_post);
1585 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1587 // Send a notification for every new post?
1588 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1589 intval($arr['contact-id']),
1592 $send_notification = count($r);
1594 if (!$send_notification) {
1595 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1596 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1599 foreach ($tags AS $tag) {
1600 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1601 normalise_link($tag["url"]), intval($arr['uid']));
1603 $send_notification = true;
1608 if ($send_notification) {
1609 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1610 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1611 intval($arr['uid']));
1613 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1614 intval($current_post),
1620 require_once('include/enotify.php');
1622 'type' => NOTIFY_SHARE,
1623 'notify_flags' => $u[0]['notify-flags'],
1624 'language' => $u[0]['language'],
1625 'to_name' => $u[0]['username'],
1626 'to_email' => $u[0]['email'],
1627 'uid' => $u[0]['uid'],
1629 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1630 'source_name' => $item[0]['author-name'],
1631 'source_link' => $item[0]['author-link'],
1632 'source_photo' => $item[0]['author-avatar'],
1633 'verb' => ACTIVITY_TAG,
1635 'parent' => $arr['parent']
1637 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1640 update_thread($parent_id);
1641 add_shadow_entry($arr);
1645 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1647 return $current_post;
1650 function item_body_set_hashtags(&$item) {
1652 $tags = get_tags($item["body"]);
1658 // This sorting is important when there are hashtags that are part of other hashtags
1659 // Otherwise there could be problems with hashtags like #test and #test2
1664 $URLSearchString = "^\[\]";
1666 // All hashtags should point to the home server
1667 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1668 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1670 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1671 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1673 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1674 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1676 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1679 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1681 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1684 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1686 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1689 // Repair recursive urls
1690 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1691 "#$2", $item["body"]);
1694 foreach($tags as $tag) {
1695 if(strpos($tag,'#') !== 0)
1698 if(strpos($tag,'[url='))
1701 $basetag = str_replace('_',' ',substr($tag,1));
1703 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1705 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1707 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1708 if(strlen($item["tag"]))
1709 $item["tag"] = ','.$item["tag"];
1710 $item["tag"] = $newtag.$item["tag"];
1714 // Convert back the masked hashtags
1715 $item["body"] = str_replace("#", "#", $item["body"]);
1718 function get_item_guid($id) {
1719 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1721 return($r[0]["guid"]);
1726 function get_item_id($guid, $uid = 0) {
1732 $uid == local_user();
1734 // Does the given user have this item?
1736 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1737 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1738 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1741 $nick = $r[0]["nickname"];
1745 // Or is it anywhere on the server?
1747 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1748 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1749 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1750 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1751 AND `item`.`private` = 0 AND `item`.`wall` = 1
1752 AND `item`.`guid` = '%s'", dbesc($guid));
1755 $nick = $r[0]["nickname"];
1758 return(array("nick" => $nick, "id" => $id));
1762 function get_item_contact($item,$contacts) {
1763 if(! count($contacts) || (! is_array($item)))
1765 foreach($contacts as $contact) {
1766 if($contact['id'] == $item['contact-id']) {
1768 break; // NOTREACHED
1775 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1777 * @param int $item_id
1778 * @return bool true if item was deleted, else false
1780 function tag_deliver($uid,$item_id) {
1788 $u = q("select * from user where uid = %d limit 1",
1794 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1795 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1798 $i = q("select * from item where id = %d and uid = %d limit 1",
1807 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1809 // Diaspora uses their own hardwired link URL in @-tags
1810 // instead of the one we supply with webfinger
1812 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1814 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1816 foreach($matches as $mtch) {
1817 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1819 logger('tag_deliver: mention found: ' . $mtch[2]);
1825 if ( ($community_page || $prvgroup) &&
1826 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1827 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1829 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1830 q("DELETE FROM item WHERE id = %d and uid = %d",
1840 // send a notification
1842 // use a local photo if we have one
1844 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1845 intval($u[0]['uid']),
1846 dbesc(normalise_link($item['author-link']))
1848 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1851 require_once('include/enotify.php');
1853 'type' => NOTIFY_TAGSELF,
1854 'notify_flags' => $u[0]['notify-flags'],
1855 'language' => $u[0]['language'],
1856 'to_name' => $u[0]['username'],
1857 'to_email' => $u[0]['email'],
1858 'uid' => $u[0]['uid'],
1860 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1861 'source_name' => $item['author-name'],
1862 'source_link' => $item['author-link'],
1863 'source_photo' => $photo,
1864 'verb' => ACTIVITY_TAG,
1866 'parent' => $item['parent']
1870 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1872 call_hooks('tagged', $arr);
1874 if((! $community_page) && (! $prvgroup))
1878 // tgroup delivery - setup a second delivery chain
1879 // prevent delivery looping - only proceed
1880 // if the message originated elsewhere and is a top-level post
1882 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1885 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1888 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1889 intval($u[0]['uid'])
1894 // also reset all the privacy bits to the forum default permissions
1896 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1898 $forum_mode = (($prvgroup) ? 2 : 1);
1900 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1901 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1902 intval($forum_mode),
1903 dbesc($c[0]['name']),
1904 dbesc($c[0]['url']),
1905 dbesc($c[0]['thumb']),
1907 dbesc($u[0]['allow_cid']),
1908 dbesc($u[0]['allow_gid']),
1909 dbesc($u[0]['deny_cid']),
1910 dbesc($u[0]['deny_gid']),
1913 update_thread($item_id);
1915 proc_run('php','include/notifier.php','tgroup',$item_id);
1921 function tgroup_check($uid,$item) {
1927 // check that the message originated elsewhere and is a top-level post
1929 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1933 $u = q("select * from user where uid = %d limit 1",
1939 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1940 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1943 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1945 // Diaspora uses their own hardwired link URL in @-tags
1946 // instead of the one we supply with webfinger
1948 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1950 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1952 foreach($matches as $mtch) {
1953 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1955 logger('tgroup_check: mention found: ' . $mtch[2]);
1963 if((! $community_page) && (! $prvgroup))
1977 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1981 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1983 if($contact['duplex'] && $contact['dfrn-id'])
1984 $idtosend = '0:' . $orig_id;
1985 if($contact['duplex'] && $contact['issued-id'])
1986 $idtosend = '1:' . $orig_id;
1989 $rino = get_config('system','rino_encrypt');
1990 $rino = intval($rino);
1995 $ssl_val = intval(get_config('system','ssl_policy'));
1999 case SSL_POLICY_FULL:
2000 $ssl_policy = 'full';
2002 case SSL_POLICY_SELFSIGN:
2003 $ssl_policy = 'self';
2005 case SSL_POLICY_NONE:
2007 $ssl_policy = 'none';
2011 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino='.$rino : '');
2013 logger('dfrn_deliver: ' . $url);
2015 $xml = fetch_url($url);
2017 $curl_stat = $a->get_curl_code();
2019 return(-1); // timed out
2021 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
2026 if(strpos($xml,'<?xml') === false) {
2027 logger('dfrn_deliver: no valid XML returned');
2028 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
2032 $res = parse_xml_string($xml);
2034 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
2035 return (($res->status) ? $res->status : 3);
2037 $postvars = array();
2038 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
2039 $challenge = hex2bin((string) $res->challenge);
2040 $perm = (($res->perm) ? $res->perm : null);
2041 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
2042 $rino_remote_version = intval($res->rino);
2043 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
2045 if($owner['page-flags'] == PAGE_PRVGROUP)
2048 $final_dfrn_id = '';
2051 if((($perm == 'rw') && (! intval($contact['writable'])))
2052 || (($perm == 'r') && (intval($contact['writable'])))) {
2053 q("update contact set writable = %d where id = %d",
2054 intval(($perm == 'rw') ? 1 : 0),
2055 intval($contact['id'])
2057 $contact['writable'] = (string) 1 - intval($contact['writable']);
2061 if(($contact['duplex'] && strlen($contact['pubkey']))
2062 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2063 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2064 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
2065 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
2068 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
2069 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
2072 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
2074 if(strpos($final_dfrn_id,':') == 1)
2075 $final_dfrn_id = substr($final_dfrn_id,2);
2077 if($final_dfrn_id != $orig_id) {
2078 logger('dfrn_deliver: wrong dfrn_id.');
2079 // did not decode properly - cannot trust this site
2083 $postvars['dfrn_id'] = $idtosend;
2084 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
2086 $postvars['dissolve'] = '1';
2089 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2090 $postvars['data'] = $atom;
2091 $postvars['perm'] = 'rw';
2094 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
2095 $postvars['perm'] = 'r';
2098 $postvars['ssl_policy'] = $ssl_policy;
2101 $postvars['page'] = $page;
2104 if($rino>0 && $rino_remote_version>0 && (! $dissolve)) {
2105 logger('rino version: '. $rino_remote_version);
2107 switch($rino_remote_version) {
2109 // Deprecated rino version!
2110 $key = substr(random_string(),0,16);
2111 $data = aes_encrypt($postvars['data'],$key);
2114 // RINO 2 based on php-encryption
2116 $key = Crypto::createNewRandomKey();
2117 } catch (CryptoTestFailed $ex) {
2118 logger('Cannot safely create a key');
2120 } catch (CannotPerformOperation $ex) {
2121 logger('Cannot safely create a key');
2125 $data = Crypto::encrypt($postvars['data'], $key);
2126 } catch (CryptoTestFailed $ex) {
2127 logger('Cannot safely perform encryption');
2129 } catch (CannotPerformOperation $ex) {
2130 logger('Cannot safely perform encryption');
2135 logger("rino: invalid requested verision '$rino_remote_version'");
2139 $postvars['rino'] = $rino_remote_version;
2140 $postvars['data'] = bin2hex($data);
2142 #logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2145 if($dfrn_version >= 2.1) {
2146 if(($contact['duplex'] && strlen($contact['pubkey']))
2147 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2148 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2150 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2153 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2157 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2158 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2161 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2165 logger('md5 rawkey ' . md5($postvars['key']));
2167 $postvars['key'] = bin2hex($postvars['key']);
2171 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2173 $xml = post_url($contact['notify'],$postvars);
2175 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2177 $curl_stat = $a->get_curl_code();
2178 if((! $curl_stat) || (! strlen($xml)))
2179 return(-1); // timed out
2181 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2184 if(strpos($xml,'<?xml') === false) {
2185 logger('dfrn_deliver: phase 2: no valid XML returned');
2186 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2190 if($contact['term-date'] != '0000-00-00 00:00:00') {
2191 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2192 require_once('include/Contact.php');
2193 unmark_for_death($contact);
2196 $res = parse_xml_string($xml);
2198 return $res->status;
2203 This function returns true if $update has an edited timestamp newer
2204 than $existing, i.e. $update contains new data which should override
2205 what's already there. If there is no timestamp yet, the update is
2206 assumed to be newer. If the update has no timestamp, the existing
2207 item is assumed to be up-to-date. If the timestamps are equal it
2208 assumes the update has been seen before and should be ignored.
2210 function edited_timestamp_is_newer($existing, $update) {
2211 if (!x($existing,'edited') || !$existing['edited']) {
2214 if (!x($update,'edited') || !$update['edited']) {
2217 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2218 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2219 return (strcmp($existing_edited, $update_edited) < 0);
2224 * consume_feed - process atom feed and update anything/everything we might need to update
2226 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2228 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2229 * It is this person's stuff that is going to be updated.
2230 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2231 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2232 * have a contact record.
2233 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2234 * might not) try and subscribe to it.
2235 * $datedir sorts in reverse order
2236 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2237 * imported prior to its children being seen in the stream unless we are certain
2238 * of how the feed is arranged/ordered.
2239 * With $pass = 1, we only pull parent items out of the stream.
2240 * With $pass = 2, we only pull children (comments/likes).
2242 * So running this twice, first with pass 1 and then with pass 2 will do the right
2243 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2244 * model where comments can have sub-threads. That would require some massive sorting
2245 * to get all the feed items into a mostly linear ordering, and might still require
2249 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2250 if ($contact['network'] === NETWORK_OSTATUS) {
2252 // Test - remove before flight
2253 //$tempfile = tempnam(get_temppath(), "ostatus");
2254 //file_put_contents($tempfile, $xml);
2256 logger("Consume OStatus messages ", LOGGER_DEBUG);
2257 ostatus_import($xml,$importer,$contact, $hub);
2262 require_once('library/simplepie/simplepie.inc');
2263 require_once('include/contact_selectors.php');
2265 if(! strlen($xml)) {
2266 logger('consume_feed: empty input');
2270 $feed = new SimplePie();
2271 $feed->set_raw_data($xml);
2273 $feed->enable_order_by_date(true);
2275 $feed->enable_order_by_date(false);
2279 logger('consume_feed: Error parsing XML: ' . $feed->error());
2281 $permalink = $feed->get_permalink();
2283 // Check at the feed level for updated contact name and/or photo
2287 $photo_timestamp = '';
2290 $contact_updated = '';
2292 $hubs = $feed->get_links('hub');
2293 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2296 $hub = implode(',', $hubs);
2298 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2300 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2302 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2303 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2304 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2305 $new_name = $elems['name'][0]['data'];
2307 // Manually checking for changed contact names
2308 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2309 $name_updated = date("c");
2310 $photo_timestamp = date("c");
2313 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2314 if ($photo_timestamp == "")
2315 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2316 $photo_url = $elems['link'][0]['attribs']['']['href'];
2319 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2320 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2324 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2325 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2327 $contact_updated = $photo_timestamp;
2329 require_once("include/Photo.php");
2330 $photo_failure = false;
2331 $have_photo = false;
2333 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2334 intval($contact['id']),
2335 intval($contact['uid'])
2338 $resource_id = $r[0]['resource-id'];
2342 $resource_id = photo_new_resource();
2345 $img_str = fetch_url($photo_url,true);
2346 // guess mimetype from headers or filename
2347 $type = guess_image_type($photo_url,true);
2350 $img = new Photo($img_str, $type);
2351 if($img->is_valid()) {
2353 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2354 dbesc($resource_id),
2355 intval($contact['id']),
2356 intval($contact['uid'])
2360 $img->scaleImageSquare(175);
2362 $hash = $resource_id;
2363 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2365 $img->scaleImage(80);
2366 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2368 $img->scaleImage(48);
2369 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2373 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2374 WHERE `uid` = %d AND `id` = %d",
2375 dbesc(datetime_convert()),
2376 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2377 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2378 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2379 intval($contact['uid']),
2380 intval($contact['id'])
2385 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2386 if ($name_updated > $contact_updated)
2387 $contact_updated = $name_updated;
2389 $r = q("select * from contact where uid = %d and id = %d limit 1",
2390 intval($contact['uid']),
2391 intval($contact['id'])
2394 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2395 dbesc(notags(trim($new_name))),
2396 dbesc(datetime_convert()),
2397 intval($contact['uid']),
2398 intval($contact['id'])
2401 // do our best to update the name on content items
2404 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2405 dbesc(notags(trim($new_name))),
2406 dbesc($r[0]['name']),
2407 dbesc($r[0]['url']),
2408 intval($contact['uid'])
2413 if ($contact_updated AND $new_name AND $photo_url)
2414 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2416 if(strlen($birthday)) {
2417 if(substr($birthday,0,4) != $contact['bdyear']) {
2418 logger('consume_feed: updating birthday: ' . $birthday);
2422 * Add new birthday event for this person
2424 * $bdtext is just a readable placeholder in case the event is shared
2425 * with others. We will replace it during presentation to our $importer
2426 * to contain a sparkle link and perhaps a photo.
2430 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2431 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2434 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2435 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2436 intval($contact['uid']),
2437 intval($contact['id']),
2438 dbesc(datetime_convert()),
2439 dbesc(datetime_convert()),
2440 dbesc(datetime_convert('UTC','UTC', $birthday)),
2441 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2450 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2451 dbesc(substr($birthday,0,4)),
2452 intval($contact['uid']),
2453 intval($contact['id'])
2456 // This function is called twice without reloading the contact
2457 // Make sure we only create one event. This is why &$contact
2458 // is a reference var in this function
2460 $contact['bdyear'] = substr($birthday,0,4);
2464 $community_page = 0;
2465 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2467 $community_page = intval($rawtags[0]['data']);
2469 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2470 q("update contact set forum = %d where id = %d",
2471 intval($community_page),
2472 intval($contact['id'])
2474 $contact['forum'] = (string) $community_page;
2478 // process any deleted entries
2480 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2481 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2482 foreach($del_entries as $dentry) {
2484 if(isset($dentry['attribs']['']['ref'])) {
2485 $uri = $dentry['attribs']['']['ref'];
2487 if(isset($dentry['attribs']['']['when'])) {
2488 $when = $dentry['attribs']['']['when'];
2489 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2492 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2494 if($deleted && is_array($contact)) {
2495 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2496 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2498 intval($importer['uid']),
2499 intval($contact['id'])
2504 if(! $item['deleted'])
2505 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2507 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2508 $xo = parse_xml_string($item['object'],false);
2509 $xt = parse_xml_string($item['target'],false);
2510 if($xt->type === ACTIVITY_OBJ_NOTE) {
2511 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2513 intval($importer['importer_uid'])
2517 // For tags, the owner cannot remove the tag on the author's copy of the post.
2519 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2520 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2521 $author_copy = (($item['origin']) ? true : false);
2523 if($owner_remove && $author_copy)
2525 if($author_remove || $owner_remove) {
2526 $tags = explode(',',$i[0]['tag']);
2529 foreach($tags as $tag)
2530 if(trim($tag) !== trim($xo->body))
2531 $newtags[] = trim($tag);
2533 q("update item set tag = '%s' where id = %d",
2534 dbesc(implode(',',$newtags)),
2537 create_tags_from_item($i[0]['id']);
2543 if($item['uri'] == $item['parent-uri']) {
2544 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2545 `body` = '', `title` = ''
2546 WHERE `parent-uri` = '%s' AND `uid` = %d",
2548 dbesc(datetime_convert()),
2549 dbesc($item['uri']),
2550 intval($importer['uid'])
2552 create_tags_from_itemuri($item['uri'], $importer['uid']);
2553 create_files_from_itemuri($item['uri'], $importer['uid']);
2554 update_thread_uri($item['uri'], $importer['uid']);
2557 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2558 `body` = '', `title` = ''
2559 WHERE `uri` = '%s' AND `uid` = %d",
2561 dbesc(datetime_convert()),
2563 intval($importer['uid'])
2565 create_tags_from_itemuri($uri, $importer['uid']);
2566 create_files_from_itemuri($uri, $importer['uid']);
2567 if($item['last-child']) {
2568 // ensure that last-child is set in case the comment that had it just got wiped.
2569 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2570 dbesc(datetime_convert()),
2571 dbesc($item['parent-uri']),
2572 intval($item['uid'])
2574 // who is the last child now?
2575 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2576 ORDER BY `created` DESC LIMIT 1",
2577 dbesc($item['parent-uri']),
2578 intval($importer['uid'])
2581 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2592 // Now process the feed
2594 if($feed->get_item_quantity()) {
2596 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2598 // in inverse date order
2600 $items = array_reverse($feed->get_items());
2602 $items = $feed->get_items();
2605 foreach($items as $item) {
2608 $item_id = $item->get_id();
2609 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2610 if(isset($rawthread[0]['attribs']['']['ref'])) {
2612 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2615 if(($is_reply) && is_array($contact)) {
2620 // not allowed to post
2622 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2626 // Have we seen it? If not, import it.
2628 $item_id = $item->get_id();
2629 $datarray = get_atom_elements($feed, $item, $contact);
2631 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2632 $datarray['author-name'] = $contact['name'];
2633 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2634 $datarray['author-link'] = $contact['url'];
2635 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2636 $datarray['author-avatar'] = $contact['thumb'];
2638 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2639 logger('consume_feed: no author information! ' . print_r($datarray,true));
2643 $force_parent = false;
2644 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2645 if($contact['network'] === NETWORK_OSTATUS)
2646 $force_parent = true;
2647 if(strlen($datarray['title']))
2648 unset($datarray['title']);
2649 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2650 dbesc(datetime_convert()),
2652 intval($importer['uid'])
2654 $datarray['last-child'] = 1;
2655 update_thread_uri($parent_uri, $importer['uid']);
2659 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2661 intval($importer['uid'])
2664 // Update content if 'updated' changes
2667 if (edited_timestamp_is_newer($r[0], $datarray)) {
2669 // do not accept (ignore) an earlier edit than one we currently have.
2670 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2673 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2674 dbesc($datarray['title']),
2675 dbesc($datarray['body']),
2676 dbesc($datarray['tag']),
2677 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2678 dbesc(datetime_convert()),
2680 intval($importer['uid'])
2682 create_tags_from_itemuri($item_id, $importer['uid']);
2683 update_thread_uri($item_id, $importer['uid']);
2686 // update last-child if it changes
2688 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2689 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2690 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2691 dbesc(datetime_convert()),
2693 intval($importer['uid'])
2695 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2696 intval($allow[0]['data']),
2697 dbesc(datetime_convert()),
2699 intval($importer['uid'])
2701 update_thread_uri($item_id, $importer['uid']);
2707 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2708 // one way feed - no remote comment ability
2709 $datarray['last-child'] = 0;
2711 $datarray['parent-uri'] = $parent_uri;
2712 $datarray['uid'] = $importer['uid'];
2713 $datarray['contact-id'] = $contact['id'];
2714 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2715 $datarray['type'] = 'activity';
2716 $datarray['gravity'] = GRAVITY_LIKE;
2717 // only one like or dislike per person
2718 // splitted into two queries for performance issues
2719 $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",
2720 intval($datarray['uid']),
2721 intval($datarray['contact-id']),
2722 dbesc($datarray['verb']),
2728 $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",
2729 intval($datarray['uid']),
2730 intval($datarray['contact-id']),
2731 dbesc($datarray['verb']),
2738 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2739 $xo = parse_xml_string($datarray['object'],false);
2740 $xt = parse_xml_string($datarray['target'],false);
2742 if($xt->type == ACTIVITY_OBJ_NOTE) {
2743 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2745 intval($importer['importer_uid'])
2750 // extract tag, if not duplicate, add to parent item
2751 if($xo->id && $xo->content) {
2752 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2753 if(! (stristr($r[0]['tag'],$newtag))) {
2754 q("UPDATE item SET tag = '%s' WHERE id = %d",
2755 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2758 create_tags_from_item($r[0]['id']);
2764 $r = item_store($datarray,$force_parent);
2770 // Head post of a conversation. Have we seen it? If not, import it.
2772 $item_id = $item->get_id();
2774 $datarray = get_atom_elements($feed, $item, $contact);
2776 if(is_array($contact)) {
2777 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2778 $datarray['author-name'] = $contact['name'];
2779 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2780 $datarray['author-link'] = $contact['url'];
2781 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2782 $datarray['author-avatar'] = $contact['thumb'];
2785 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2786 logger('consume_feed: no author information! ' . print_r($datarray,true));
2790 // special handling for events
2792 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2793 $ev = bbtoevent($datarray['body']);
2794 if(x($ev,'desc') && x($ev,'start')) {
2795 $ev['uid'] = $importer['uid'];
2796 $ev['uri'] = $item_id;
2797 $ev['edited'] = $datarray['edited'];
2798 $ev['private'] = $datarray['private'];
2800 if(is_array($contact))
2801 $ev['cid'] = $contact['id'];
2802 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2804 intval($importer['uid'])
2807 $ev['id'] = $r[0]['id'];
2808 $xyz = event_store($ev);
2813 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2814 if(strlen($datarray['title']))
2815 unset($datarray['title']);
2816 $datarray['last-child'] = 1;
2820 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2822 intval($importer['uid'])
2825 // Update content if 'updated' changes
2828 if (edited_timestamp_is_newer($r[0], $datarray)) {
2830 // do not accept (ignore) an earlier edit than one we currently have.
2831 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2834 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2835 dbesc($datarray['title']),
2836 dbesc($datarray['body']),
2837 dbesc($datarray['tag']),
2838 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2839 dbesc(datetime_convert()),
2841 intval($importer['uid'])
2843 create_tags_from_itemuri($item_id, $importer['uid']);
2844 update_thread_uri($item_id, $importer['uid']);
2847 // update last-child if it changes
2849 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2850 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2851 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2852 intval($allow[0]['data']),
2853 dbesc(datetime_convert()),
2855 intval($importer['uid'])
2857 update_thread_uri($item_id, $importer['uid']);
2862 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2863 logger('consume-feed: New follower');
2864 new_follower($importer,$contact,$datarray,$item);
2867 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2868 lose_follower($importer,$contact,$datarray,$item);
2872 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2873 logger('consume-feed: New friend request');
2874 new_follower($importer,$contact,$datarray,$item,true);
2877 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2878 lose_sharer($importer,$contact,$datarray,$item);
2883 if(! is_array($contact))
2887 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2888 // one way feed - no remote comment ability
2889 $datarray['last-child'] = 0;
2891 if($contact['network'] === NETWORK_FEED)
2892 $datarray['private'] = 2;
2894 $datarray['parent-uri'] = $item_id;
2895 $datarray['uid'] = $importer['uid'];
2896 $datarray['contact-id'] = $contact['id'];
2898 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2899 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2900 // but otherwise there's a possible data mixup on the sender's system.
2901 // the tgroup delivery code called from item_store will correct it if it's a forum,
2902 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2903 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2904 $datarray['owner-name'] = $contact['name'];
2905 $datarray['owner-link'] = $contact['url'];
2906 $datarray['owner-avatar'] = $contact['thumb'];
2909 // We've allowed "followers" to reach this point so we can decide if they are
2910 // posting an @-tag delivery, which followers are allowed to do for certain
2911 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2913 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2916 // This is my contact on another system, but it's really me.
2917 // Turn this into a wall post.
2918 $notify = item_is_remote_self($contact, $datarray);
2920 $r = item_store($datarray, false, $notify);
2921 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2929 function item_is_remote_self($contact, &$datarray) {
2932 if (!$contact['remote_self'])
2935 // Prevent the forwarding of posts that are forwarded
2936 if ($datarray["extid"] == NETWORK_DFRN)
2939 // Prevent to forward already forwarded posts
2940 if ($datarray["app"] == $a->get_hostname())
2943 // Only forward posts
2944 if ($datarray["verb"] != ACTIVITY_POST)
2947 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2950 $datarray2 = $datarray;
2951 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2952 if ($contact['remote_self'] == 2) {
2953 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2954 intval($contact['uid']));
2956 $datarray['contact-id'] = $r[0]["id"];
2958 $datarray['owner-name'] = $r[0]["name"];
2959 $datarray['owner-link'] = $r[0]["url"];
2960 $datarray['owner-avatar'] = $r[0]["thumb"];
2962 $datarray['author-name'] = $datarray['owner-name'];
2963 $datarray['author-link'] = $datarray['owner-link'];
2964 $datarray['author-avatar'] = $datarray['owner-avatar'];
2967 if ($contact['network'] != NETWORK_FEED) {
2968 $datarray["guid"] = get_guid(32);
2969 unset($datarray["plink"]);
2970 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid']);
2971 $datarray["parent-uri"] = $datarray["uri"];
2972 $datarray["extid"] = $contact['network'];
2973 $urlpart = parse_url($datarray2['author-link']);
2974 $datarray["app"] = $urlpart["host"];
2976 $datarray['private'] = 0;
2979 //if (!isset($datarray["app"]) OR ($datarray["app"] == ""))
2980 // $datarray["app"] = network_to_name($contact['network']);
2982 if ($contact['network'] != NETWORK_FEED) {
2983 // Store the original post
2984 $r = item_store($datarray2, false, false);
2985 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2987 $datarray["app"] = "Feed";
2992 function local_delivery($importer,$data) {
2995 logger(__function__, LOGGER_TRACE);
2997 if($importer['readonly']) {
2998 // We aren't receiving stuff from this person. But we will quietly ignore them
2999 // rather than a blatant "go away" message.
3000 logger('local_delivery: ignoring');
3005 // Consume notification feed. This may differ from consuming a public feed in several ways
3006 // - might contain email or friend suggestions
3007 // - might contain remote followup to our message
3008 // - in which case we need to accept it and then notify other conversants
3009 // - we may need to send various email notifications
3011 $feed = new SimplePie();
3012 $feed->set_raw_data($data);
3013 $feed->enable_order_by_date(false);
3018 logger('local_delivery: Error parsing XML: ' . $feed->error());
3021 // Check at the feed level for updated contact name and/or photo
3025 $photo_timestamp = '';
3027 $contact_updated = '';
3030 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
3032 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
3034 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
3037 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
3038 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
3039 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
3040 $new_name = $elems['name'][0]['data'];
3042 // Manually checking for changed contact names
3043 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
3044 $name_updated = date("c");
3045 $photo_timestamp = date("c");
3048 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
3049 if ($photo_timestamp == "")
3050 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
3051 $photo_url = $elems['link'][0]['attribs']['']['href'];
3055 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
3057 $contact_updated = $photo_timestamp;
3059 logger('local_delivery: Updating photo for ' . $importer['name']);
3060 require_once("include/Photo.php");
3061 $photo_failure = false;
3062 $have_photo = false;
3064 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
3065 intval($importer['id']),
3066 intval($importer['importer_uid'])
3069 $resource_id = $r[0]['resource-id'];
3073 $resource_id = photo_new_resource();
3076 $img_str = fetch_url($photo_url,true);
3077 // guess mimetype from headers or filename
3078 $type = guess_image_type($photo_url,true);
3081 $img = new Photo($img_str, $type);
3082 if($img->is_valid()) {
3084 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
3085 dbesc($resource_id),
3086 intval($importer['id']),
3087 intval($importer['importer_uid'])
3091 $img->scaleImageSquare(175);
3093 $hash = $resource_id;
3094 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
3096 $img->scaleImage(80);
3097 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
3099 $img->scaleImage(48);
3100 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
3104 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
3105 WHERE `uid` = %d AND `id` = %d",
3106 dbesc(datetime_convert()),
3107 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
3108 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
3109 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
3110 intval($importer['importer_uid']),
3111 intval($importer['id'])
3116 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
3117 if ($name_updated > $contact_updated)
3118 $contact_updated = $name_updated;
3120 $r = q("select * from contact where uid = %d and id = %d limit 1",
3121 intval($importer['importer_uid']),
3122 intval($importer['id'])
3125 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
3126 dbesc(notags(trim($new_name))),
3127 dbesc(datetime_convert()),
3128 intval($importer['importer_uid']),
3129 intval($importer['id'])
3132 // do our best to update the name on content items
3135 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
3136 dbesc(notags(trim($new_name))),
3137 dbesc($r[0]['name']),
3138 dbesc($r[0]['url']),
3139 intval($importer['importer_uid'])
3144 if ($contact_updated AND $new_name AND $photo_url)
3145 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3147 // Currently unsupported - needs a lot of work
3148 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3149 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3150 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3152 $newloc['uid'] = $importer['importer_uid'];
3153 $newloc['cid'] = $importer['id'];
3154 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3155 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3156 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3157 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3158 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3159 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3160 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3161 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3162 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3163 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3164 /** relocated user must have original key pair */
3165 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3166 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3168 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3171 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3172 intval($importer['id']),
3173 intval($importer['importer_uid']));
3178 $x = q("UPDATE contact SET
3189 `site-pubkey` = '%s'
3190 WHERE id=%d AND uid=%d;",
3191 dbesc($newloc['name']),
3192 dbesc($newloc['photo']),
3193 dbesc($newloc['thumb']),
3194 dbesc($newloc['micro']),
3195 dbesc($newloc['url']),
3196 dbesc(normalise_link($newloc['url'])),
3197 dbesc($newloc['request']),
3198 dbesc($newloc['confirm']),
3199 dbesc($newloc['notify']),
3200 dbesc($newloc['poll']),
3201 dbesc($newloc['sitepubkey']),
3202 intval($importer['id']),
3203 intval($importer['importer_uid']));
3209 'owner-link' => array($old['url'], $newloc['url']),
3210 'author-link' => array($old['url'], $newloc['url']),
3211 'owner-avatar' => array($old['photo'], $newloc['photo']),
3212 'author-avatar' => array($old['photo'], $newloc['photo']),
3214 foreach ($fields as $n=>$f){
3215 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3218 intval($importer['importer_uid']));
3224 // merge with current record, current contents have priority
3225 // update record, set url-updated
3226 // update profile photos
3232 // handle friend suggestion notification
3234 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3235 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3236 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3238 $fsugg['uid'] = $importer['importer_uid'];
3239 $fsugg['cid'] = $importer['id'];
3240 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3241 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3242 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3243 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3244 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3246 // Does our member already have a friend matching this description?
3248 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3249 dbesc($fsugg['name']),
3250 dbesc(normalise_link($fsugg['url'])),
3251 intval($fsugg['uid'])
3256 // Do we already have an fcontact record for this person?
3259 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3260 dbesc($fsugg['url']),
3261 dbesc($fsugg['name']),
3262 dbesc($fsugg['request'])
3267 // OK, we do. Do we already have an introduction for this person ?
3268 $r = q("select id from intro where uid = %d and fid = %d limit 1",
3269 intval($fsugg['uid']),
3276 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3277 dbesc($fsugg['name']),
3278 dbesc($fsugg['url']),
3279 dbesc($fsugg['photo']),
3280 dbesc($fsugg['request'])
3282 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3283 dbesc($fsugg['url']),
3284 dbesc($fsugg['name']),
3285 dbesc($fsugg['request'])
3290 // database record did not get created. Quietly give up.
3295 $hash = random_string();
3297 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3298 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3299 intval($fsugg['uid']),
3301 intval($fsugg['cid']),
3302 dbesc($fsugg['body']),
3304 dbesc(datetime_convert()),
3309 'type' => NOTIFY_SUGGEST,
3310 'notify_flags' => $importer['notify-flags'],
3311 'language' => $importer['language'],
3312 'to_name' => $importer['username'],
3313 'to_email' => $importer['email'],
3314 'uid' => $importer['importer_uid'],
3316 'link' => $a->get_baseurl() . '/notifications/intros',
3317 'source_name' => $importer['name'],
3318 'source_link' => $importer['url'],
3319 'source_photo' => $importer['photo'],
3320 'verb' => ACTIVITY_REQ_FRIEND,
3329 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3330 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3332 logger('local_delivery: private message received');
3335 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3338 $msg['uid'] = $importer['importer_uid'];
3339 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3340 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3341 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3342 $msg['contact-id'] = $importer['id'];
3343 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3344 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3346 $msg['replied'] = 0;
3347 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3348 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3349 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3353 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3354 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3356 // send notifications.
3358 require_once('include/enotify.php');
3360 $notif_params = array(
3361 'type' => NOTIFY_MAIL,
3362 'notify_flags' => $importer['notify-flags'],
3363 'language' => $importer['language'],
3364 'to_name' => $importer['username'],
3365 'to_email' => $importer['email'],
3366 'uid' => $importer['importer_uid'],
3368 'source_name' => $msg['from-name'],
3369 'source_link' => $importer['url'],
3370 'source_photo' => $importer['thumb'],
3371 'verb' => ACTIVITY_POST,
3375 notification($notif_params);
3381 $community_page = 0;
3382 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3384 $community_page = intval($rawtags[0]['data']);
3386 if(intval($importer['forum']) != $community_page) {
3387 q("update contact set forum = %d where id = %d",
3388 intval($community_page),
3389 intval($importer['id'])
3391 $importer['forum'] = (string) $community_page;
3394 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3396 // process any deleted entries
3398 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3399 if(is_array($del_entries) && count($del_entries)) {
3400 foreach($del_entries as $dentry) {
3402 if(isset($dentry['attribs']['']['ref'])) {
3403 $uri = $dentry['attribs']['']['ref'];
3405 if(isset($dentry['attribs']['']['when'])) {
3406 $when = $dentry['attribs']['']['when'];
3407 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3410 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3414 // check for relayed deletes to our conversation
3417 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3419 intval($importer['importer_uid'])
3422 $parent_uri = $r[0]['parent-uri'];
3423 if($r[0]['id'] != $r[0]['parent'])
3430 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3433 logger('local_delivery: possible community delete');
3436 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3438 // was the top-level post for this reply written by somebody on this site?
3439 // Specifically, the recipient?
3441 $is_a_remote_delete = false;
3443 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3444 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3445 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3446 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3447 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3448 AND `item`.`uid` = %d
3454 intval($importer['importer_uid'])
3457 $is_a_remote_delete = true;
3459 // Does this have the characteristics of a community or private group comment?
3460 // If it's a reply to a wall post on a community/prvgroup page it's a
3461 // valid community comment. Also forum_mode makes it valid for sure.
3462 // If neither, it's not.
3464 if($is_a_remote_delete && $community) {
3465 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3466 $is_a_remote_delete = false;
3467 logger('local_delivery: not a community delete');
3471 if($is_a_remote_delete) {
3472 logger('local_delivery: received remote delete');
3476 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3477 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3479 intval($importer['importer_uid']),
3480 intval($importer['id'])
3486 if($item['deleted'])
3489 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3491 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3492 $xo = parse_xml_string($item['object'],false);
3493 $xt = parse_xml_string($item['target'],false);
3495 if($xt->type === ACTIVITY_OBJ_NOTE) {
3496 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3498 intval($importer['importer_uid'])
3502 // For tags, the owner cannot remove the tag on the author's copy of the post.
3504 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3505 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3506 $author_copy = (($item['origin']) ? true : false);
3508 if($owner_remove && $author_copy)
3510 if($author_remove || $owner_remove) {
3511 $tags = explode(',',$i[0]['tag']);
3514 foreach($tags as $tag)
3515 if(trim($tag) !== trim($xo->body))
3516 $newtags[] = trim($tag);
3518 q("update item set tag = '%s' where id = %d",
3519 dbesc(implode(',',$newtags)),
3522 create_tags_from_item($i[0]['id']);
3528 if($item['uri'] == $item['parent-uri']) {
3529 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3530 `body` = '', `title` = ''
3531 WHERE `parent-uri` = '%s' AND `uid` = %d",
3533 dbesc(datetime_convert()),
3534 dbesc($item['uri']),
3535 intval($importer['importer_uid'])
3537 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3538 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3539 update_thread_uri($item['uri'], $importer['importer_uid']);
3542 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3543 `body` = '', `title` = ''
3544 WHERE `uri` = '%s' AND `uid` = %d",
3546 dbesc(datetime_convert()),
3548 intval($importer['importer_uid'])
3550 create_tags_from_itemuri($uri, $importer['importer_uid']);
3551 create_files_from_itemuri($uri, $importer['importer_uid']);
3552 update_thread_uri($uri, $importer['importer_uid']);
3553 if($item['last-child']) {
3554 // ensure that last-child is set in case the comment that had it just got wiped.
3555 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3556 dbesc(datetime_convert()),
3557 dbesc($item['parent-uri']),
3558 intval($item['uid'])
3560 // who is the last child now?
3561 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3562 ORDER BY `created` DESC LIMIT 1",
3563 dbesc($item['parent-uri']),
3564 intval($importer['importer_uid'])
3567 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3572 // if this is a relayed delete, propagate it to other recipients
3574 if($is_a_remote_delete)
3575 proc_run('php',"include/notifier.php","drop",$item['id']);
3583 foreach($feed->get_items() as $item) {
3586 $item_id = $item->get_id();
3587 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3588 if(isset($rawthread[0]['attribs']['']['ref'])) {
3590 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3596 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3599 logger('local_delivery: possible community reply');
3602 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3604 // was the top-level post for this reply written by somebody on this site?
3605 // Specifically, the recipient?
3607 $is_a_remote_comment = false;
3608 $top_uri = $parent_uri;
3610 $r = q("select `item`.`parent-uri` from `item`
3611 WHERE `item`.`uri` = '%s'
3615 if($r && count($r)) {
3616 $top_uri = $r[0]['parent-uri'];
3618 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3619 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3620 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3621 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3622 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3623 AND `item`.`uid` = %d
3629 intval($importer['importer_uid'])
3632 $is_a_remote_comment = true;
3635 // Does this have the characteristics of a community or private group comment?
3636 // If it's a reply to a wall post on a community/prvgroup page it's a
3637 // valid community comment. Also forum_mode makes it valid for sure.
3638 // If neither, it's not.
3640 if($is_a_remote_comment && $community) {
3641 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3642 $is_a_remote_comment = false;
3643 logger('local_delivery: not a community reply');
3647 if($is_a_remote_comment) {
3648 logger('local_delivery: received remote comment');
3650 // remote reply to our post. Import and then notify everybody else.
3652 $datarray = get_atom_elements($feed, $item);
3654 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3656 intval($importer['importer_uid'])
3659 // Update content if 'updated' changes
3663 if (edited_timestamp_is_newer($r[0], $datarray)) {
3665 // do not accept (ignore) an earlier edit than one we currently have.
3666 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3669 logger('received updated comment' , LOGGER_DEBUG);
3670 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3671 dbesc($datarray['title']),
3672 dbesc($datarray['body']),
3673 dbesc($datarray['tag']),
3674 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3675 dbesc(datetime_convert()),
3677 intval($importer['importer_uid'])
3679 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3681 proc_run('php',"include/notifier.php","comment-import",$iid);
3690 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3691 intval($importer['importer_uid'])
3695 $datarray['type'] = 'remote-comment';
3696 $datarray['wall'] = 1;
3697 $datarray['parent-uri'] = $parent_uri;
3698 $datarray['uid'] = $importer['importer_uid'];
3699 $datarray['owner-name'] = $own[0]['name'];
3700 $datarray['owner-link'] = $own[0]['url'];
3701 $datarray['owner-avatar'] = $own[0]['thumb'];
3702 $datarray['contact-id'] = $importer['id'];
3704 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3706 $datarray['type'] = 'activity';
3707 $datarray['gravity'] = GRAVITY_LIKE;
3708 $datarray['last-child'] = 0;
3709 // only one like or dislike per person
3710 // splitted into two queries for performance issues
3711 $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",
3712 intval($datarray['uid']),
3713 intval($datarray['contact-id']),
3714 dbesc($datarray['verb']),
3715 dbesc($datarray['parent-uri'])
3721 $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",
3722 intval($datarray['uid']),
3723 intval($datarray['contact-id']),
3724 dbesc($datarray['verb']),
3725 dbesc($datarray['parent-uri'])
3732 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3734 $xo = parse_xml_string($datarray['object'],false);
3735 $xt = parse_xml_string($datarray['target'],false);
3737 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3739 // fetch the parent item
3741 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3743 intval($importer['importer_uid'])
3748 // extract tag, if not duplicate, and this user allows tags, add to parent item
3750 if($xo->id && $xo->content) {
3751 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3752 if(! (stristr($tagp[0]['tag'],$newtag))) {
3753 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3754 intval($importer['importer_uid'])
3756 if(count($i) && ! intval($i[0]['blocktags'])) {
3757 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3758 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3759 intval($tagp[0]['id']),
3760 dbesc(datetime_convert()),
3761 dbesc(datetime_convert())
3763 create_tags_from_item($tagp[0]['id']);
3771 $posted_id = item_store($datarray);
3776 $datarray["id"] = $posted_id;
3778 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3780 intval($importer['importer_uid'])
3783 $parent = $r[0]['parent'];
3784 $parent_uri = $r[0]['parent-uri'];
3788 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3789 dbesc(datetime_convert()),
3790 intval($importer['importer_uid']),
3791 intval($r[0]['parent'])
3794 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3795 dbesc(datetime_convert()),
3796 intval($importer['importer_uid']),
3801 if($posted_id && $parent) {
3803 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3805 if((! $is_like) && (! $importer['self'])) {
3807 require_once('include/enotify.php');
3810 'type' => NOTIFY_COMMENT,
3811 'notify_flags' => $importer['notify-flags'],
3812 'language' => $importer['language'],
3813 'to_name' => $importer['username'],
3814 'to_email' => $importer['email'],
3815 'uid' => $importer['importer_uid'],
3816 'item' => $datarray,
3817 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3818 'source_name' => stripslashes($datarray['author-name']),
3819 'source_link' => $datarray['author-link'],
3820 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3821 ? $importer['thumb'] : $datarray['author-avatar']),
3822 'verb' => ACTIVITY_POST,
3824 'parent' => $parent,
3825 'parent_uri' => $parent_uri,
3837 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3839 $item_id = $item->get_id();
3840 $datarray = get_atom_elements($feed,$item);
3842 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3845 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3847 intval($importer['importer_uid'])
3850 // Update content if 'updated' changes
3853 if (edited_timestamp_is_newer($r[0], $datarray)) {
3855 // do not accept (ignore) an earlier edit than one we currently have.
3856 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3859 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3860 dbesc($datarray['title']),
3861 dbesc($datarray['body']),
3862 dbesc($datarray['tag']),
3863 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3864 dbesc(datetime_convert()),
3866 intval($importer['importer_uid'])
3868 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3871 // update last-child if it changes
3873 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3874 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3875 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3876 dbesc(datetime_convert()),
3878 intval($importer['importer_uid'])
3880 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3881 intval($allow[0]['data']),
3882 dbesc(datetime_convert()),
3884 intval($importer['importer_uid'])
3890 $datarray['parent-uri'] = $parent_uri;
3891 $datarray['uid'] = $importer['importer_uid'];
3892 $datarray['contact-id'] = $importer['id'];
3893 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3894 $datarray['type'] = 'activity';
3895 $datarray['gravity'] = GRAVITY_LIKE;
3896 // only one like or dislike per person
3897 // splitted into two queries for performance issues
3898 $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",
3899 intval($datarray['uid']),
3900 intval($datarray['contact-id']),
3901 dbesc($datarray['verb']),
3907 $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",
3908 intval($datarray['uid']),
3909 intval($datarray['contact-id']),
3910 dbesc($datarray['verb']),
3918 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3920 $xo = parse_xml_string($datarray['object'],false);
3921 $xt = parse_xml_string($datarray['target'],false);
3923 if($xt->type == ACTIVITY_OBJ_NOTE) {
3924 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3926 intval($importer['importer_uid'])
3931 // extract tag, if not duplicate, add to parent item
3933 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3934 q("UPDATE item SET tag = '%s' WHERE id = %d",
3935 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3938 create_tags_from_item($r[0]['id']);
3944 $posted_id = item_store($datarray);
3946 // find out if our user is involved in this conversation and wants to be notified.
3948 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3950 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3952 intval($importer['importer_uid'])
3955 if(count($myconv)) {
3956 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3958 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3959 if(! link_compare($datarray['author-link'],$importer_url)) {
3962 foreach($myconv as $conv) {
3964 // now if we find a match, it means we're in this conversation
3966 if(! link_compare($conv['author-link'],$importer_url))
3969 require_once('include/enotify.php');
3971 $conv_parent = $conv['parent'];
3974 'type' => NOTIFY_COMMENT,
3975 'notify_flags' => $importer['notify-flags'],
3976 'language' => $importer['language'],
3977 'to_name' => $importer['username'],
3978 'to_email' => $importer['email'],
3979 'uid' => $importer['importer_uid'],
3980 'item' => $datarray,
3981 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3982 'source_name' => stripslashes($datarray['author-name']),
3983 'source_link' => $datarray['author-link'],
3984 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3985 ? $importer['thumb'] : $datarray['author-avatar']),
3986 'verb' => ACTIVITY_POST,
3988 'parent' => $conv_parent,
3989 'parent_uri' => $parent_uri
3993 // only send one notification
4005 // Head post of a conversation. Have we seen it? If not, import it.
4008 $item_id = $item->get_id();
4009 $datarray = get_atom_elements($feed,$item);
4011 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
4012 $ev = bbtoevent($datarray['body']);
4013 if(x($ev,'desc') && x($ev,'start')) {
4014 $ev['cid'] = $importer['id'];
4015 $ev['uid'] = $importer['uid'];
4016 $ev['uri'] = $item_id;
4017 $ev['edited'] = $datarray['edited'];
4018 $ev['private'] = $datarray['private'];
4020 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4022 intval($importer['uid'])
4025 $ev['id'] = $r[0]['id'];
4026 $xyz = event_store($ev);
4031 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4033 intval($importer['importer_uid'])
4036 // Update content if 'updated' changes
4039 if (edited_timestamp_is_newer($r[0], $datarray)) {
4041 // do not accept (ignore) an earlier edit than one we currently have.
4042 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
4045 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4046 dbesc($datarray['title']),
4047 dbesc($datarray['body']),
4048 dbesc($datarray['tag']),
4049 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
4050 dbesc(datetime_convert()),
4052 intval($importer['importer_uid'])
4054 create_tags_from_itemuri($item_id, $importer['importer_uid']);
4055 update_thread_uri($item_id, $importer['importer_uid']);
4058 // update last-child if it changes
4060 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
4061 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
4062 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4063 intval($allow[0]['data']),
4064 dbesc(datetime_convert()),
4066 intval($importer['importer_uid'])
4072 $datarray['parent-uri'] = $item_id;
4073 $datarray['uid'] = $importer['importer_uid'];
4074 $datarray['contact-id'] = $importer['id'];
4077 if(! link_compare($datarray['owner-link'],$importer['url'])) {
4078 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
4079 // but otherwise there's a possible data mixup on the sender's system.
4080 // the tgroup delivery code called from item_store will correct it if it's a forum,
4081 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
4082 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
4083 $datarray['owner-name'] = $importer['senderName'];
4084 $datarray['owner-link'] = $importer['url'];
4085 $datarray['owner-avatar'] = $importer['thumb'];
4088 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
4091 // This is my contact on another system, but it's really me.
4092 // Turn this into a wall post.
4093 $notify = item_is_remote_self($importer, $datarray);
4095 $posted_id = item_store($datarray, false, $notify);
4097 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
4098 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
4101 $xo = parse_xml_string($datarray['object'],false);
4103 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
4105 // somebody was poked/prodded. Was it me?
4107 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
4109 foreach($links->link as $l) {
4110 $atts = $l->attributes();
4111 switch($atts['rel']) {
4113 $Blink = $atts['href'];
4119 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
4121 // send a notification
4122 require_once('include/enotify.php');
4125 'type' => NOTIFY_POKE,
4126 'notify_flags' => $importer['notify-flags'],
4127 'language' => $importer['language'],
4128 'to_name' => $importer['username'],
4129 'to_email' => $importer['email'],
4130 'uid' => $importer['importer_uid'],
4131 'item' => $datarray,
4132 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4133 'source_name' => stripslashes($datarray['author-name']),
4134 'source_link' => $datarray['author-link'],
4135 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4136 ? $importer['thumb'] : $datarray['author-avatar']),
4137 'verb' => $datarray['verb'],
4138 'otype' => 'person',
4139 'activity' => $verb,
4140 'parent' => $datarray['parent']
4156 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4157 $url = notags(trim($datarray['author-link']));
4158 $name = notags(trim($datarray['author-name']));
4159 $photo = notags(trim($datarray['author-avatar']));
4161 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4162 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4163 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4165 if(is_array($contact)) {
4166 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4167 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4168 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4169 intval(CONTACT_IS_FRIEND),
4170 intval($contact['id']),
4171 intval($importer['uid'])
4174 // send email notification to owner?
4178 // create contact record
4180 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4181 `blocked`, `readonly`, `pending`, `writable` )
4182 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4183 intval($importer['uid']),
4184 dbesc(datetime_convert()),
4186 dbesc(normalise_link($url)),
4190 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4191 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4193 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4194 intval($importer['uid']),
4198 $contact_record = $r[0];
4200 // create notification
4201 $hash = random_string();
4203 if(is_array($contact_record)) {
4204 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4205 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4206 intval($importer['uid']),
4207 intval($contact_record['id']),
4209 dbesc(datetime_convert())
4213 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4214 intval($importer['uid'])
4219 if(intval($r[0]['def_gid'])) {
4220 require_once('include/group.php');
4221 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4224 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4225 in_array($r[0]['page-flags'], array(PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE))) {
4228 'type' => NOTIFY_INTRO,
4229 'notify_flags' => $r[0]['notify-flags'],
4230 'language' => $r[0]['language'],
4231 'to_name' => $r[0]['username'],
4232 'to_email' => $r[0]['email'],
4233 'uid' => $r[0]['uid'],
4234 'link' => $a->get_baseurl() . '/notifications/intro',
4235 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4236 'source_link' => $contact_record['url'],
4237 'source_photo' => $contact_record['photo'],
4238 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4247 function lose_follower($importer,$contact,$datarray,$item) {
4249 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4250 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4251 intval(CONTACT_IS_SHARING),
4252 intval($contact['id'])
4256 contact_remove($contact['id']);
4260 function lose_sharer($importer,$contact,$datarray,$item) {
4262 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4263 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4264 intval(CONTACT_IS_FOLLOWER),
4265 intval($contact['id'])
4269 contact_remove($contact['id']);
4274 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4278 if(is_array($importer)) {
4279 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4280 intval($importer['uid'])
4284 // Diaspora has different message-ids in feeds than they do
4285 // through the direct Diaspora protocol. If we try and use
4286 // the feed, we'll get duplicates. So don't.
4288 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4291 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4293 // Use a single verify token, even if multiple hubs
4295 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4297 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4299 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4301 if(! strlen($contact['hub-verify'])) {
4302 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4303 dbesc($verify_token),
4304 intval($contact['id'])
4308 post_url($url,$params);
4310 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4317 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4321 $name = xmlify($name);
4322 $uri = xmlify($uri);
4325 $photo = xmlify($photo);
4329 $o .= "\t<name>$name</name>\r\n";
4330 $o .= "\t<uri>$uri</uri>\r\n";
4331 $o .= "\t".'<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4332 $o .= "\t".'<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4334 if ($tag == "author") {
4335 $r = q("SELECT `profile`.`locality`, `profile`.`region`, `profile`.`country-name`,
4336 `profile`.`name`, `profile`.`pub_keywords`, `profile`.`about`,
4337 `profile`.`homepage`,`contact`.`nick` FROM `profile`
4338 INNER JOIN `contact` ON `contact`.`uid` = `profile`.`uid`
4339 INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
4340 WHERE `profile`.`is-default` AND `contact`.`self` AND
4341 NOT `user`.`hidewall` AND `contact`.`nurl`='%s'",
4342 dbesc(normalise_link($uri)));
4345 if($r[0]['locality'])
4346 $location .= $r[0]['locality'];
4347 if($r[0]['region']) {
4350 $location .= $r[0]['region'];
4352 if($r[0]['country-name']) {
4355 $location .= $r[0]['country-name'];
4358 $o .= "\t<poco:preferredUsername>".xmlify($r[0]["nick"])."</poco:preferredUsername>\r\n";
4359 $o .= "\t<poco:displayName>".xmlify($r[0]["name"])."</poco:displayName>\r\n";
4360 $o .= "\t<poco:note>".xmlify($r[0]["about"])."</poco:note>\r\n";
4361 $o .= "\t<poco:address>\r\n";
4362 $o .= "\t\t<poco:formatted>".xmlify($location)."</poco:formatted>\r\n";
4363 $o .= "\t</poco:address>\r\n";
4364 $o .= "\t<poco:urls>\r\n";
4365 $o .= "\t<poco:type>homepage</poco:type>\r\n";
4366 $o .= "\t\t<poco:value>".xmlify($r[0]["homepage"])."</poco:value>\r\n";
4367 $o .= "\t\t<poco:primary>true</poco:primary>\r\n";
4368 $o .= "\t</poco:urls>\r\n";
4372 call_hooks('atom_author', $o);
4374 $o .= "</$tag>\r\n";
4378 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4382 if(! $item['parent'])
4385 if($item['deleted'])
4386 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4389 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4390 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4392 $body = $item['body'];
4395 $o = "\r\n\r\n<entry>\r\n";
4397 if(is_array($author))
4398 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4400 $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']));
4401 if(strlen($item['owner-name']))
4402 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4404 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4405 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4406 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
4411 if ($item['title'] != "")
4412 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4414 //$htmlbody = bbcode(bb_remove_share_information($htmlbody), false, false, 7);
4415 $htmlbody = bbcode($htmlbody, false, false, 7);
4417 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4418 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4419 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4420 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4421 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4422 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4423 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
4426 $o .= '<status_net notice_id="'.$item['id'].'"></status_net>'."\r\n";
4429 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4431 if($item['location']) {
4432 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4433 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4437 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4439 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4440 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4443 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4444 if($item['bookmark'])
4445 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4448 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4451 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4453 if($item['signed_text']) {
4454 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4455 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4458 $verb = construct_verb($item);
4459 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4460 $actobj = construct_activity_object($item);
4463 $actarg = construct_activity_target($item);
4467 $tags = item_getfeedtags($item);
4469 foreach($tags as $t)
4470 if (($type != 'html') OR ($t[0] != "@"))
4471 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4475 // To support these elements, the API needs to be enhanced
4476 //$o .= '<link rel="ostatus:conversation" href="'.xmlify($a->get_baseurl().'/display/'.$owner['nickname'].'/'.$item['parent']).'"/>'."\r\n";
4477 //$o .= "\t".'<link rel="self" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4478 //$o .= "\t".'<link rel="edit" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4480 $o .= item_get_attachment($item);
4482 $o .= item_getfeedattach($item);
4484 $mentioned = get_mentions($item);
4488 call_hooks('atom_entry', $o);
4490 $o .= '</entry>' . "\r\n";
4495 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4497 if(get_config('system','disable_embedded'))
4502 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4503 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4508 $img_start = strpos($orig_body, '[img');
4509 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4510 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4511 while( ($img_st_close !== false) && ($img_len !== false) ) {
4513 $img_st_close++; // make it point to AFTER the closing bracket
4514 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4516 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4519 if(stristr($image , $site . '/photo/')) {
4520 // Only embed locally hosted photos
4522 $i = basename($image);
4523 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4524 $x = strpos($i,'-');
4527 $res = substr($i,$x+1);
4528 $i = substr($i,0,$x);
4529 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4536 // Check to see if we should replace this photo link with an embedded image
4537 // 1. No need to do so if the photo is public
4538 // 2. If there's a contact-id provided, see if they're in the access list
4539 // for the photo. If so, embed it.
4540 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4541 // permissions, regardless of order but first check to see if they're an exact
4542 // match to save some processing overhead.
4544 if(has_permissions($r[0])) {
4546 $recips = enumerate_permissions($r[0]);
4547 if(in_array($cid, $recips)) {
4552 if(compare_permissions($item,$r[0]))
4557 $data = $r[0]['data'];
4558 $type = $r[0]['type'];
4560 // If a custom width and height were specified, apply before embedding
4561 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4562 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4564 $width = intval($match[1]);
4565 $height = intval($match[2]);
4567 $ph = new Photo($data, $type);
4568 if($ph->is_valid()) {
4569 $ph->scaleImage(max($width, $height));
4570 $data = $ph->imageString();
4571 $type = $ph->getType();
4575 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4576 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4577 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4583 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4584 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4585 if($orig_body === false)
4588 $img_start = strpos($orig_body, '[img');
4589 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4590 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4593 $new_body = $new_body . $orig_body;
4599 function has_permissions($obj) {
4600 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4605 function compare_permissions($obj1,$obj2) {
4606 // first part is easy. Check that these are exactly the same.
4607 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4608 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4609 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4610 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4613 // This is harder. Parse all the permissions and compare the resulting set.
4615 $recipients1 = enumerate_permissions($obj1);
4616 $recipients2 = enumerate_permissions($obj2);
4619 if($recipients1 == $recipients2)
4624 // returns an array of contact-ids that are allowed to see this object
4626 function enumerate_permissions($obj) {
4627 require_once('include/group.php');
4628 $allow_people = expand_acl($obj['allow_cid']);
4629 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4630 $deny_people = expand_acl($obj['deny_cid']);
4631 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4632 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4633 $deny = array_unique(array_merge($deny_people,$deny_groups));
4634 $recipients = array_diff($recipients,$deny);
4638 function item_getfeedtags($item) {
4641 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4643 for($x = 0; $x < $cnt; $x ++) {
4645 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4649 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4651 for($x = 0; $x < $cnt; $x ++) {
4653 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4659 function item_get_attachment($item) {
4661 $siteinfo = get_attached_data($item["body"]);
4663 switch($siteinfo["type"]) {
4665 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4668 $imgdata = get_photo_info($siteinfo["image"]);
4669 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["image"]).'" type="'.$imgdata["mime"].'" length="'.$imgdata["size"].'"/>'."\r\n";
4672 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4681 function item_getfeedattach($item) {
4683 $arr = explode('[/attach],',$item['attach']);
4685 foreach($arr as $r) {
4687 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4689 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4690 if(intval($matches[2]))
4691 $ret .= 'length="' . intval($matches[2]) . '" ';
4692 if($matches[4] !== ' ')
4693 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4694 $ret .= ' />' . "\r\n";
4703 function item_expire($uid, $days, $network = "", $force = false) {
4705 if((! $uid) || ($days < 1))
4708 // $expire_network_only = save your own wall posts
4709 // and just expire conversations started by others
4711 $expire_network_only = get_pconfig($uid,'expire','network_only');
4712 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4714 if ($network != "") {
4715 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4716 // There is an index "uid_network_received" but not "uid_network_created"
4717 // This avoids the creation of another index just for one purpose.
4718 // And it doesn't really matter wether to look at "received" or "created"
4719 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4721 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4723 $r = q("SELECT * FROM `item`
4724 WHERE `uid` = %d $range
4735 $expire_items = get_pconfig($uid, 'expire','items');
4736 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4738 // Forcing expiring of items - but not notes and marked items
4740 $expire_items = true;
4742 $expire_notes = get_pconfig($uid, 'expire','notes');
4743 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4745 $expire_starred = get_pconfig($uid, 'expire','starred');
4746 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4748 $expire_photos = get_pconfig($uid, 'expire','photos');
4749 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4751 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4753 foreach($r as $item) {
4755 // don't expire filed items
4757 if(strpos($item['file'],'[') !== false)
4760 // Only expire posts, not photos and photo comments
4762 if($expire_photos==0 && strlen($item['resource-id']))
4764 if($expire_starred==0 && intval($item['starred']))
4766 if($expire_notes==0 && $item['type']=='note')
4768 if($expire_items==0 && $item['type']!='note')
4771 drop_item($item['id'],false);
4774 proc_run('php',"include/notifier.php","expire","$uid");
4779 function drop_items($items) {
4782 if(! local_user() && ! remote_user())
4786 foreach($items as $item) {
4787 $owner = drop_item($item,false);
4788 if($owner && ! $uid)
4793 // multiple threads may have been deleted, send an expire notification
4796 proc_run('php',"include/notifier.php","expire","$uid");
4800 function drop_item($id,$interactive = true) {
4804 // locate item to be deleted
4806 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4813 notice( t('Item not found.') . EOL);
4814 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4819 $owner = $item['uid'];
4823 // check if logged in user is either the author or owner of this item
4825 if(is_array($_SESSION['remote'])) {
4826 foreach($_SESSION['remote'] as $visitor) {
4827 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4828 $cid = $visitor['cid'];
4835 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4837 // Check if we should do HTML-based delete confirmation
4838 if($_REQUEST['confirm']) {
4839 // <form> can't take arguments in its "action" parameter
4840 // so add any arguments as hidden inputs
4841 $query = explode_querystring($a->query_string);
4843 foreach($query['args'] as $arg) {
4844 if(strpos($arg, 'confirm=') === false) {
4845 $arg_parts = explode('=', $arg);
4846 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4850 return replace_macros(get_markup_template('confirm.tpl'), array(
4852 '$message' => t('Do you really want to delete this item?'),
4853 '$extra_inputs' => $inputs,
4854 '$confirm' => t('Yes'),
4855 '$confirm_url' => $query['base'],
4856 '$confirm_name' => 'confirmed',
4857 '$cancel' => t('Cancel'),
4860 // Now check how the user responded to the confirmation query
4861 if($_REQUEST['canceled']) {
4862 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4865 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4868 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4869 dbesc(datetime_convert()),
4870 dbesc(datetime_convert()),
4873 create_tags_from_item($item['id']);
4874 create_files_from_item($item['id']);
4875 delete_thread($item['id'], $item['parent-uri']);
4877 // clean up categories and tags so they don't end up as orphans
4880 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4882 foreach($matches as $mtch) {
4883 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4889 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4891 foreach($matches as $mtch) {
4892 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4896 // If item is a link to a photo resource, nuke all the associated photos
4897 // (visitors will not have photo resources)
4898 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4899 // generate a resource-id and therefore aren't intimately linked to the item.
4901 if(strlen($item['resource-id'])) {
4902 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4903 dbesc($item['resource-id']),
4904 intval($item['uid'])
4906 // ignore the result
4909 // If item is a link to an event, nuke the event record.
4911 if(intval($item['event-id'])) {
4912 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4913 intval($item['event-id']),
4914 intval($item['uid'])
4916 // ignore the result
4919 // If item has attachments, drop them
4921 foreach(explode(",",$item['attach']) as $attach){
4922 preg_match("|attach/(\d+)|", $attach, $matches);
4923 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4924 intval($matches[1]),
4927 // ignore the result
4931 // clean up item_id and sign meta-data tables
4934 // Old code - caused very long queries and warning entries in the mysql logfiles:
4936 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4937 intval($item['id']),
4938 intval($item['uid'])
4941 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4942 intval($item['id']),
4943 intval($item['uid'])
4947 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4949 // Creating list of parents
4950 $r = q("select id from item where parent = %d and uid = %d",
4951 intval($item['id']),
4952 intval($item['uid'])
4957 foreach ($r AS $row) {
4958 if ($parentid != "")
4961 $parentid .= $row["id"];
4965 if ($parentid != "") {
4966 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4968 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4971 // If it's the parent of a comment thread, kill all the kids
4973 if($item['uri'] == $item['parent-uri']) {
4974 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4975 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4976 dbesc(datetime_convert()),
4977 dbesc(datetime_convert()),
4978 dbesc($item['parent-uri']),
4979 intval($item['uid'])
4981 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4982 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4983 delete_thread_uri($item['parent-uri'], $item['uid']);
4984 // ignore the result
4987 // ensure that last-child is set in case the comment that had it just got wiped.
4988 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4989 dbesc(datetime_convert()),
4990 dbesc($item['parent-uri']),
4991 intval($item['uid'])
4993 // who is the last child now?
4994 $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",
4995 dbesc($item['parent-uri']),
4996 intval($item['uid'])
4999 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
5004 // Add a relayable_retraction signature for Diaspora.
5005 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
5008 $drop_id = intval($item['id']);
5010 // send the notification upstream/downstream as the case may be
5012 proc_run('php',"include/notifier.php","drop","$drop_id");
5016 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5022 notice( t('Permission denied.') . EOL);
5023 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5030 function first_post_date($uid,$wall = false) {
5031 $r = q("select id, created from item
5032 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
5034 order by created asc limit 1",
5036 intval($wall ? 1 : 0)
5039 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
5040 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
5045 /* modified posted_dates() {below} to arrange the list in years */
5046 function list_post_dates($uid, $wall) {
5047 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5049 $dthen = first_post_date($uid, $wall);
5053 // Set the start and end date to the beginning of the month
5054 $dnow = substr($dnow,0,8).'01';
5055 $dthen = substr($dthen,0,8).'01';
5059 // Starting with the current month, get the first and last days of every
5060 // month down to and including the month of the first post
5061 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5062 $dyear = intval(substr($dnow,0,4));
5063 $dstart = substr($dnow,0,8) . '01';
5064 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5065 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5066 $end_month = datetime_convert('','',$dend,'Y-m-d');
5067 $str = day_translate(datetime_convert('','',$dnow,'F'));
5069 $ret[$dyear] = array();
5070 $ret[$dyear][] = array($str,$end_month,$start_month);
5071 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5076 function posted_dates($uid,$wall) {
5077 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5079 $dthen = first_post_date($uid,$wall);
5083 // Set the start and end date to the beginning of the month
5084 $dnow = substr($dnow,0,8).'01';
5085 $dthen = substr($dthen,0,8).'01';
5088 // Starting with the current month, get the first and last days of every
5089 // month down to and including the month of the first post
5090 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5091 $dstart = substr($dnow,0,8) . '01';
5092 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5093 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5094 $end_month = datetime_convert('','',$dend,'Y-m-d');
5095 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
5096 $ret[] = array($str,$end_month,$start_month);
5097 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5103 function posted_date_widget($url,$uid,$wall) {
5106 if(! feature_enabled($uid,'archives'))
5109 // For former Facebook folks that left because of "timeline"
5111 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
5114 $visible_years = get_pconfig($uid,'system','archive_visible_years');
5115 if(! $visible_years)
5118 $ret = list_post_dates($uid,$wall);
5123 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
5124 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
5126 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
5127 '$title' => t('Archives'),
5128 '$size' => $visible_years,
5129 '$cutoff_year' => $cutoff_year,
5130 '$cutoff' => $cutoff,
5133 '$showmore' => t('show more')
5139 function store_diaspora_retract_sig($item, $user, $baseurl) {
5140 // Note that we can't add a target_author_signature
5141 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
5142 // the comment, that means we're the home of the post, and Diaspora will only
5143 // check the parent_author_signature of retractions that it doesn't have to relay further
5145 // I don't think this function gets called for an "unlike," but I'll check anyway
5147 $enabled = intval(get_config('system','diaspora_enabled'));
5149 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
5153 logger('drop_item: storing diaspora retraction signature');
5155 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
5157 if(local_user() == $item['uid']) {
5159 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
5160 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
5163 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
5164 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
5167 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
5168 // only handles DFRN deletes
5169 $handle_baseurl_start = strpos($r['url'],'://') + 3;
5170 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
5171 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
5177 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
5178 intval($item['id']),
5179 dbesc($signed_text),