diff --git a/boot.php b/boot.php
index 2242ba476d..955b89e56c 100644
--- a/boot.php
+++ b/boot.php
@@ -530,6 +530,8 @@ class App {
 	private $cached_profile_image;
 	private $cached_profile_picdate;
 
+	private static $a;
+
 	/**
 	 * @brief App constructor.
 	 */
@@ -710,6 +712,8 @@ class App {
 			}
 		}
 
+		self::$a = $this;
+
 	}
 
 	function get_basepath() {
@@ -734,6 +738,10 @@ class App {
 
 	function get_baseurl($ssl = false) {
 
+		// Is the function called statically?
+		if (!is_object($this))
+			return(self::$a->get_baseurl($ssl));
+
 		$scheme = $this->scheme;
 
 		if((x($this->config,'system')) && (x($this->config['system'],'ssl_policy'))) {
diff --git a/include/Contact.php b/include/Contact.php
index 831a99c950..d9703d61e4 100644
--- a/include/Contact.php
+++ b/include/Contact.php
@@ -132,8 +132,8 @@ function terminate_friendship($user,$self,$contact) {
 		diaspora_unshare($user,$contact);
 	}
 	elseif($contact['network'] === NETWORK_DFRN) {
-		require_once('include/items.php');
-		dfrn_deliver($user,$contact,'placeholder', 1);
+		require_once('include/dfrn.php');
+		dfrn::deliver($user,$contact,'placeholder', 1);
 	}
 
 }
diff --git a/include/delivery.php b/include/delivery.php
index d046005994..5ef942dd06 100644
--- a/include/delivery.php
+++ b/include/delivery.php
@@ -279,12 +279,12 @@ function delivery_run(&$argv, &$argc){
 
 				if ($mail) {
 					$item['body'] = fix_private_photos($item['body'],$owner['uid'],null,$message[0]['contact-id']);
-					$atom = dfrn_mail($item, $owner);
+					$atom = dfrn::mail($item, $owner);
 				} elseif ($fsuggest) {
-					$atom = dfrn_fsuggest($item, $owner);
+					$atom = dfrn::fsuggest($item, $owner);
 					q("DELETE FROM `fsuggest` WHERE `id` = %d LIMIT 1", intval($item['id']));
 				} elseif ($relocate)
-					$atom = dfrn_relocate($owner, $uid);
+					$atom = dfrn::relocate($owner, $uid);
 				elseif($followup) {
 					$msgitems = array();
 					foreach($items as $item) {  // there is only one item
@@ -295,7 +295,7 @@ function delivery_run(&$argv, &$argc){
 							$msgitems[] = $item;
 						}
 					}
-					$atom = dfrn_entries($msgitems,$owner);
+					$atom = dfrn::entries($msgitems,$owner);
 				} else {
 					$msgitems = array();
 					foreach($items as $item) {
@@ -321,7 +321,7 @@ function delivery_run(&$argv, &$argc){
 							$msgitems[] = $item;
 						}
 					}
-					$atom = dfrn_entries($msgitems,$owner);
+					$atom = dfrn::entries($msgitems,$owner);
 				}
 
 				logger('notifier entry: '.$contact["url"].' '.$target_item["guid"].' entry: '.$atom, LOGGER_DEBUG);
@@ -380,7 +380,7 @@ function delivery_run(&$argv, &$argc){
 				}
 
 				if(! was_recently_delayed($contact['id']))
-					$deliver_status = dfrn_deliver($owner,$contact,$atom);
+					$deliver_status = dfrn::deliver($owner,$contact,$atom);
 				else
 					$deliver_status = (-1);
 
diff --git a/include/dfrn.php b/include/dfrn.php
index a951e11fc9..885e03a91b 100644
--- a/include/dfrn.php
+++ b/include/dfrn.php
@@ -3,210 +3,240 @@ require_once('include/items.php');
 require_once('include/Contact.php');
 require_once('include/ostatus.php');
 
-/**
- * @brief Generates the atom entries for delivery.php
- *
- * This function is used whenever content is transmitted via DFRN.
- *
- * @param array $items Item elements
- * @param array $owner Owner record
- *
- * @return string DFRN entries
- */
-function dfrn_entries($items,$owner) {
+class dfrn {
 
-	$doc = new DOMDocument('1.0', 'utf-8');
-	$doc->formatOutput = true;
+	/**
+	 * @brief Generates the atom entries for delivery.php
+	 *
+	 * This function is used whenever content is transmitted via DFRN.
+	 *
+	 * @param array $items Item elements
+	 * @param array $owner Owner record
+	 *
+	 * @return string DFRN entries
+	 */
+	function entries($items,$owner) {
 
-	$root = dfrn_add_header($doc, $owner, "dfrn:owner", "", false);
+		$doc = new DOMDocument('1.0', 'utf-8');
+		$doc->formatOutput = true;
 
-	if(! count($items))
-		return trim($doc->saveXML());
+		$root = self::add_header($doc, $owner, "dfrn:owner", "", false);
 
-	foreach($items as $item) {
-		$entry = dfrn_entry($doc, "text", $item, $owner, $item["entry:comment-allow"], $item["entry:cid"]);
-		$root->appendChild($entry);
-	}
+		if(! count($items))
+			return trim($doc->saveXML());
 
-	return(trim($doc->saveXML()));
-}
-
-/**
- * @brief Generate an atom feed for the given user
- *
- * This function is called when another server is pulling data from the user feed.
- *
- * @param App $a
- * @param string $dfrn_id DFRN ID from the requesting party
- * @param string $owner_nick Owner nick name
- * @param string $last_update Date of the last update
- * @param int $direction Can be -1, 0 or 1.
- *
- * @return string DFRN feed entries
- */
-function dfrn_feed(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) {
-
-	$sitefeed    = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
-	$public_feed = (($dfrn_id) ? false : true);
-	$starred     = false;   // not yet implemented, possible security issues
-	$converse    = false;
-
-	if($public_feed && $a->argc > 2) {
-		for($x = 2; $x < $a->argc; $x++) {
-			if($a->argv[$x] == 'converse')
-				$converse = true;
-			if($a->argv[$x] == 'starred')
-				$starred = true;
-			if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
-				$category = $a->argv[$x+1];
-		}
-	}
-
-
-
-	// default permissions - anonymous user
-
-	$sql_extra = " AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid`  = '' AND `item`.`deny_gid`  = '' ";
-
-	$r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
-		FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
-		WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
-		dbesc($owner_nick)
-	);
-
-	if(! count($r))
-		killme();
-
-	$owner = $r[0];
-	$owner_id = $owner['user_uid'];
-	$owner_nick = $owner['nickname'];
-
-	$sql_post_table = "";
-	$visibility = "";
-
-	if(! $public_feed) {
-
-		$sql_extra = '';
-		switch($direction) {
-			case (-1):
-				$sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
-				$my_id = $dfrn_id;
-				break;
-			case 0:
-				$sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
-				$my_id = '1:' . $dfrn_id;
-				break;
-			case 1:
-				$sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
-				$my_id = '0:' . $dfrn_id;
-				break;
-			default:
-				return false;
-				break; // NOTREACHED
+		foreach($items as $item) {
+			$entry = self::entry($doc, "text", $item, $owner, $item["entry:comment-allow"], $item["entry:cid"]);
+			$root->appendChild($entry);
 		}
 
-		$r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
-			intval($owner_id)
+		return(trim($doc->saveXML()));
+	}
+
+	/**
+	 * @brief Generate an atom feed for the given user
+	 *
+	 * This function is called when another server is pulling data from the user feed.
+	 *
+	 * @param string $dfrn_id DFRN ID from the requesting party
+	 * @param string $owner_nick Owner nick name
+	 * @param string $last_update Date of the last update
+	 * @param int $direction Can be -1, 0 or 1.
+	 *
+	 * @return string DFRN feed entries
+	 */
+	function feed($dfrn_id, $owner_nick, $last_update, $direction = 0) {
+
+		$a = get_app();
+
+		$sitefeed    = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
+		$public_feed = (($dfrn_id) ? false : true);
+		$starred     = false;   // not yet implemented, possible security issues
+		$converse    = false;
+
+		if($public_feed && $a->argc > 2) {
+			for($x = 2; $x < $a->argc; $x++) {
+				if($a->argv[$x] == 'converse')
+					$converse = true;
+				if($a->argv[$x] == 'starred')
+					$starred = true;
+				if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
+					$category = $a->argv[$x+1];
+			}
+		}
+
+
+
+		// default permissions - anonymous user
+
+		$sql_extra = " AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid`  = '' AND `item`.`deny_gid`  = '' ";
+
+		$r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
+			FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
+			WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
+			dbesc($owner_nick)
 		);
 
 		if(! count($r))
 			killme();
 
-		$contact = $r[0];
-		require_once('include/security.php');
-		$groups = init_groups_visitor($contact['id']);
+		$owner = $r[0];
+		$owner_id = $owner['user_uid'];
+		$owner_nick = $owner['nickname'];
 
-		if(count($groups)) {
-			for($x = 0; $x < count($groups); $x ++)
-				$groups[$x] = '<' . intval($groups[$x]) . '>' ;
-			$gs = implode('|', $groups);
+		$sql_post_table = "";
+		$visibility = "";
+
+		if(! $public_feed) {
+
+			$sql_extra = '';
+			switch($direction) {
+				case (-1):
+					$sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
+					$my_id = $dfrn_id;
+					break;
+				case 0:
+					$sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
+					$my_id = '1:' . $dfrn_id;
+					break;
+				case 1:
+					$sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
+					$my_id = '0:' . $dfrn_id;
+					break;
+				default:
+					return false;
+					break; // NOTREACHED
+			}
+
+			$r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
+				intval($owner_id)
+			);
+
+			if(! count($r))
+				killme();
+
+			$contact = $r[0];
+			require_once('include/security.php');
+			$groups = init_groups_visitor($contact['id']);
+
+			if(count($groups)) {
+				for($x = 0; $x < count($groups); $x ++)
+					$groups[$x] = '<' . intval($groups[$x]) . '>' ;
+				$gs = implode('|', $groups);
+			} else
+				$gs = '<<>>' ; // Impossible to match
+
+			$sql_extra = sprintf("
+				AND ( `allow_cid` = '' OR     `allow_cid` REGEXP '<%d>' )
+				AND ( `deny_cid`  = '' OR NOT `deny_cid`  REGEXP '<%d>' )
+				AND ( `allow_gid` = '' OR     `allow_gid` REGEXP '%s' )
+				AND ( `deny_gid`  = '' OR NOT `deny_gid`  REGEXP '%s')
+			",
+				intval($contact['id']),
+				intval($contact['id']),
+				dbesc($gs),
+				dbesc($gs)
+			);
 		}
+
+		if($public_feed)
+			$sort = 'DESC';
 		else
-			$gs = '<<>>' ; // Impossible to match
+			$sort = 'ASC';
 
-		$sql_extra = sprintf("
-			AND ( `allow_cid` = '' OR     `allow_cid` REGEXP '<%d>' )
-			AND ( `deny_cid`  = '' OR NOT `deny_cid`  REGEXP '<%d>' )
-			AND ( `allow_gid` = '' OR     `allow_gid` REGEXP '%s' )
-			AND ( `deny_gid`  = '' OR NOT `deny_gid`  REGEXP '%s')
-		",
-			intval($contact['id']),
-			intval($contact['id']),
-			dbesc($gs),
-			dbesc($gs)
+		$date_field = "`changed`";
+		$sql_order = "`item`.`parent` ".$sort.", `item`.`created` ASC";
+
+		if(! strlen($last_update))
+			$last_update = 'now -30 days';
+
+		if(isset($category)) {
+			$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` ",
+					dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
+			//$sql_extra .= file_tag_file_query('item',$category,'category');
+		}
+
+		if($public_feed) {
+			if(! $converse)
+				$sql_extra .= " AND `contact`.`self` = 1 ";
+		}
+
+		$check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
+
+		//	AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
+		//	dbesc($check_date),
+
+		$r = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id`,
+			`contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
+			`contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
+			`contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
+			`contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
+			`sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
+			FROM `item` $sql_post_table
+			INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
+			AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
+			LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
+			WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
+			AND ((`item`.`wall` = 1) $visibility) AND `item`.$date_field > '%s'
+			$sql_extra
+			ORDER BY $sql_order LIMIT 0, 300",
+			intval($owner_id),
+			dbesc($check_date),
+			dbesc($sort)
 		);
-	}
 
-	if($public_feed)
-		$sort = 'DESC';
-	else
-		$sort = 'ASC';
+		// Will check further below if this actually returned results.
+		// We will provide an empty feed if that is the case.
 
-	$date_field = "`changed`";
-	$sql_order = "`item`.`parent` ".$sort.", `item`.`created` ASC";
+		$items = $r;
 
-	if(! strlen($last_update))
-		$last_update = 'now -30 days';
+		$doc = new DOMDocument('1.0', 'utf-8');
+		$doc->formatOutput = true;
 
-	if(isset($category)) {
-		$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` ",
-				dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
-		//$sql_extra .= file_tag_file_query('item',$category,'category');
-	}
+		$alternatelink = $owner['url'];
 
-	if($public_feed) {
-		if(! $converse)
-			$sql_extra .= " AND `contact`.`self` = 1 ";
-	}
+		if(isset($category))
+			$alternatelink .= "/category/".$category;
 
-	$check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
+		if ($public_feed)
+			$author = "dfrn:owner";
+		else
+			$author = "author";
 
-	//	AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
-	//	dbesc($check_date),
+		$root = self::add_header($doc, $owner, $author, $alternatelink, true);
 
-	$r = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id`,
-		`contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
-		`contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
-		`contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
-		`contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
-		`sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
-		FROM `item` $sql_post_table
-		INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
-		AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
-		LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
-		WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
-		AND ((`item`.`wall` = 1) $visibility) AND `item`.$date_field > '%s'
-		$sql_extra
-		ORDER BY $sql_order LIMIT 0, 300",
-		intval($owner_id),
-		dbesc($check_date),
-		dbesc($sort)
-	);
+		// This hook can't work anymore
+		//	call_hooks('atom_feed', $atom);
 
-	// Will check further below if this actually returned results.
-	// We will provide an empty feed if that is the case.
+		if(! count($items)) {
+			$atom = trim($doc->saveXML());
 
-	$items = $r;
+			call_hooks('atom_feed_end', $atom);
 
-	$doc = new DOMDocument('1.0', 'utf-8');
-	$doc->formatOutput = true;
+			return $atom;
+		}
 
-	$alternatelink = $owner['url'];
+		foreach($items as $item) {
 
-	if(isset($category))
-		$alternatelink .= "/category/".$category;
+			// prevent private email from leaking.
+			if($item['network'] === NETWORK_MAIL)
+				continue;
 
-	if ($public_feed)
-		$author = "dfrn:owner";
-	else
-		$author = "author";
+			// public feeds get html, our own nodes use bbcode
 
-	$root = dfrn_add_header($doc, $owner, $author, $alternatelink, true);
+			if($public_feed) {
+				$type = 'html';
+				// catch any email that's in a public conversation and make sure it doesn't leak
+				if($item['private'])
+					continue;
+			} else
+				$type = 'text';
 
-	// This hook can't work anymore
-	//	call_hooks('atom_feed', $atom);
+			$entry = self::entry($doc, $type, $item, $owner, true);
+			$root->appendChild($entry);
+
+		}
 
-	if(! count($items)) {
 		$atom = trim($doc->saveXML());
 
 		call_hooks('atom_feed_end', $atom);
@@ -214,581 +244,781 @@ function dfrn_feed(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) {
 		return $atom;
 	}
 
-	foreach($items as $item) {
+	/**
+	 * @brief Create XML text for DFRN mails
+	 *
+	 * @param array $item message elements
+	 * @param array $owner Owner record
+	 *
+	 * @return string DFRN mail
+	 */
+	function mail($item, $owner) {
+		$doc = new DOMDocument('1.0', 'utf-8');
+		$doc->formatOutput = true;
 
-		// prevent private email from leaking.
-		if($item['network'] === NETWORK_MAIL)
-			continue;
+		$root = self::add_header($doc, $owner, "dfrn:owner", "", false);
 
-		// public feeds get html, our own nodes use bbcode
+		$mail = $doc->createElement("dfrn:mail");
+		$sender = $doc->createElement("dfrn:sender");
 
-		if($public_feed) {
-			$type = 'html';
-			// catch any email that's in a public conversation and make sure it doesn't leak
-			if($item['private'])
-				continue;
-		}
-		else {
-			$type = 'text';
+		xml_add_element($doc, $sender, "dfrn:name", $owner['name']);
+		xml_add_element($doc, $sender, "dfrn:uri", $owner['url']);
+		xml_add_element($doc, $sender, "dfrn:avatar", $owner['thumb']);
+
+		$mail->appendChild($sender);
+
+		xml_add_element($doc, $mail, "dfrn:id", $item['uri']);
+		xml_add_element($doc, $mail, "dfrn:in-reply-to", $item['parent-uri']);
+		xml_add_element($doc, $mail, "dfrn:sentdate", datetime_convert('UTC', 'UTC', $item['created'] . '+00:00' , ATOM_TIME));
+		xml_add_element($doc, $mail, "dfrn:subject", $item['title']);
+		xml_add_element($doc, $mail, "dfrn:content", $item['body']);
+
+		$root->appendChild($mail);
+
+		return(trim($doc->saveXML()));
+	}
+
+	/**
+	 * @brief Create XML text for DFRN friend suggestions
+	 *
+	 * @param array $item suggestion elements
+	 * @param array $owner Owner record
+	 *
+	 * @return string DFRN suggestions
+	 */
+	function fsuggest($item, $owner) {
+		$doc = new DOMDocument('1.0', 'utf-8');
+		$doc->formatOutput = true;
+
+		$root = self::add_header($doc, $owner, "dfrn:owner", "", false);
+
+		$suggest = $doc->createElement("dfrn:suggest");
+
+		xml_add_element($doc, $suggest, "dfrn:url", $item['url']);
+		xml_add_element($doc, $suggest, "dfrn:name", $item['name']);
+		xml_add_element($doc, $suggest, "dfrn:photo", $item['photo']);
+		xml_add_element($doc, $suggest, "dfrn:request", $item['request']);
+		xml_add_element($doc, $suggest, "dfrn:note", $item['note']);
+
+		$root->appendChild($suggest);
+
+		return(trim($doc->saveXML()));
+	}
+
+	/**
+	 * @brief Create XML text for DFRN relocations
+	 *
+	 * @param array $owner Owner record
+	 * @param int $uid User ID
+	 *
+	 * @return string DFRN relocations
+	 */
+	function relocate($owner, $uid) {
+
+		/* get site pubkey. this could be a new installation with no site keys*/
+		$pubkey = get_config('system','site_pubkey');
+		if(! $pubkey) {
+			$res = new_keypair(1024);
+			set_config('system','site_prvkey', $res['prvkey']);
+			set_config('system','site_pubkey', $res['pubkey']);
 		}
 
-		$entry = dfrn_entry($doc, $type, $item, $owner, true);
-		$root->appendChild($entry);
+		$rp = q("SELECT `resource-id` , `scale`, type FROM `photo`
+				WHERE `profile` = 1 AND `uid` = %d ORDER BY scale;", $uid);
+		$photos = array();
+		$ext = Photo::supportedTypes();
 
+		foreach($rp as $p)
+			$photos[$p['scale']] = app::get_baseurl().'/photo/'.$p['resource-id'].'-'.$p['scale'].'.'.$ext[$p['type']];
+
+		unset($rp, $ext);
+
+		$doc = new DOMDocument('1.0', 'utf-8');
+		$doc->formatOutput = true;
+
+		$root = self::add_header($doc, $owner, "dfrn:owner", "", false);
+
+		$relocate = $doc->createElement("dfrn:relocate");
+
+		xml_add_element($doc, $relocate, "dfrn:url", $owner['url']);
+		xml_add_element($doc, $relocate, "dfrn:name", $owner['name']);
+		xml_add_element($doc, $relocate, "dfrn:photo", $photos[4]);
+		xml_add_element($doc, $relocate, "dfrn:thumb", $photos[5]);
+		xml_add_element($doc, $relocate, "dfrn:micro", $photos[6]);
+		xml_add_element($doc, $relocate, "dfrn:request", $owner['request']);
+		xml_add_element($doc, $relocate, "dfrn:confirm", $owner['confirm']);
+		xml_add_element($doc, $relocate, "dfrn:notify", $owner['notify']);
+		xml_add_element($doc, $relocate, "dfrn:poll", $owner['poll']);
+		xml_add_element($doc, $relocate, "dfrn:sitepubkey", get_config('system','site_pubkey'));
+
+		$root->appendChild($relocate);
+
+		return(trim($doc->saveXML()));
 	}
 
-	$atom = trim($doc->saveXML());
+	/**
+	 * @brief Adds the header elements for the DFRN protocol
+	 *
+	 * @param object $doc XML document
+	 * @param array $owner Owner record
+	 * @param string $authorelement Element name for the author
+	 * @param string $alternatelink link to profile or category
+	 * @param bool $public Is it a header for public posts?
+	 *
+	 * @return object XML root object
+	 */
+	private function add_header($doc, $owner, $authorelement, $alternatelink = "", $public = false) {
 
-	call_hooks('atom_feed_end', $atom);
+		if ($alternatelink == "")
+			$alternatelink = $owner['url'];
 
-	return $atom;
-}
+		$root = $doc->createElementNS(NS_ATOM, 'feed');
+		$doc->appendChild($root);
 
-/**
- * @brief Create XML text for DFRN mails
- *
- * @param array $item message elements
- * @param array $owner Owner record
- *
- * @return string DFRN mail
- */
-function dfrn_mail($item, $owner) {
-	$doc = new DOMDocument('1.0', 'utf-8');
-	$doc->formatOutput = true;
+		$root->setAttribute("xmlns:thr", NS_THR);
+		$root->setAttribute("xmlns:at", "http://purl.org/atompub/tombstones/1.0");
+		$root->setAttribute("xmlns:media", NS_MEDIA);
+		$root->setAttribute("xmlns:dfrn", "http://purl.org/macgirvin/dfrn/1.0");
+		$root->setAttribute("xmlns:activity", NS_ACTIVITY);
+		$root->setAttribute("xmlns:georss", NS_GEORSS);
+		$root->setAttribute("xmlns:poco", NS_POCO);
+		$root->setAttribute("xmlns:ostatus", NS_OSTATUS);
+		$root->setAttribute("xmlns:statusnet", NS_STATUSNET);
 
-	$root = dfrn_add_header($doc, $owner, "dfrn:owner", "", false);
+		//xml_add_element($doc, $root, "id", app::get_baseurl()."/profile/".$owner["nick"]);
+		xml_add_element($doc, $root, "id", app::get_baseurl()."/profile/".$owner["nick"]);
+		xml_add_element($doc, $root, "title", $owner["name"]);
 
-	$mail = $doc->createElement("dfrn:mail");
-	$sender = $doc->createElement("dfrn:sender");
+		$attributes = array("uri" => "https://friendi.ca", "version" => FRIENDICA_VERSION."-".DB_UPDATE_VERSION);
+		xml_add_element($doc, $root, "generator", FRIENDICA_PLATFORM, $attributes);
 
-	xml_add_element($doc, $sender, "dfrn:name", $owner['name']);
-	xml_add_element($doc, $sender, "dfrn:uri", $owner['url']);
-	xml_add_element($doc, $sender, "dfrn:avatar", $owner['thumb']);
-
-	$mail->appendChild($sender);
-
-	xml_add_element($doc, $mail, "dfrn:id", $item['uri']);
-	xml_add_element($doc, $mail, "dfrn:in-reply-to", $item['parent-uri']);
-	xml_add_element($doc, $mail, "dfrn:sentdate", datetime_convert('UTC', 'UTC', $item['created'] . '+00:00' , ATOM_TIME));
-	xml_add_element($doc, $mail, "dfrn:subject", $item['title']);
-	xml_add_element($doc, $mail, "dfrn:content", $item['body']);
-
-	$root->appendChild($mail);
-
-	return(trim($doc->saveXML()));
-}
-
-/**
- * @brief Create XML text for DFRN friend suggestions
- *
- * @param array $item suggestion elements
- * @param array $owner Owner record
- *
- * @return string DFRN suggestions
- */
-function dfrn_fsuggest($item, $owner) {
-	$doc = new DOMDocument('1.0', 'utf-8');
-	$doc->formatOutput = true;
-
-	$root = dfrn_add_header($doc, $owner, "dfrn:owner", "", false);
-
-	$suggest = $doc->createElement("dfrn:suggest");
-
-	xml_add_element($doc, $suggest, "dfrn:url", $item['url']);
-	xml_add_element($doc, $suggest, "dfrn:name", $item['name']);
-	xml_add_element($doc, $suggest, "dfrn:photo", $item['photo']);
-	xml_add_element($doc, $suggest, "dfrn:request", $item['request']);
-	xml_add_element($doc, $suggest, "dfrn:note", $item['note']);
-
-	$root->appendChild($suggest);
-
-	return(trim($doc->saveXML()));
-}
-
-/**
- * @brief Create XML text for DFRN relocations
- *
- * @param array $owner Owner record
- * @param int $uid User ID
- *
- * @return string DFRN relocations
- */
-function dfrn_relocate($owner, $uid) {
-
-	$a = get_app();
-
-	/* get site pubkey. this could be a new installation with no site keys*/
-	$pubkey = get_config('system','site_pubkey');
-	if(! $pubkey) {
-		$res = new_keypair(1024);
-		set_config('system','site_prvkey', $res['prvkey']);
-		set_config('system','site_pubkey', $res['pubkey']);
-	}
-
-	$rp = q("SELECT `resource-id` , `scale`, type FROM `photo`
-			WHERE `profile` = 1 AND `uid` = %d ORDER BY scale;", $uid);
-	$photos = array();
-	$ext = Photo::supportedTypes();
-	foreach($rp as $p){
-		$photos[$p['scale']] = $a->get_baseurl().'/photo/'.$p['resource-id'].'-'.$p['scale'].'.'.$ext[$p['type']];
-	}
-	unset($rp, $ext);
-
-	$doc = new DOMDocument('1.0', 'utf-8');
-	$doc->formatOutput = true;
-
-	$root = dfrn_add_header($doc, $owner, "dfrn:owner", "", false);
-
-	$relocate = $doc->createElement("dfrn:relocate");
-
-	xml_add_element($doc, $relocate, "dfrn:url", $owner['url']);
-	xml_add_element($doc, $relocate, "dfrn:name", $owner['name']);
-	xml_add_element($doc, $relocate, "dfrn:photo", $photos[4]);
-	xml_add_element($doc, $relocate, "dfrn:thumb", $photos[5]);
-	xml_add_element($doc, $relocate, "dfrn:micro", $photos[6]);
-	xml_add_element($doc, $relocate, "dfrn:request", $owner['request']);
-	xml_add_element($doc, $relocate, "dfrn:confirm", $owner['confirm']);
-	xml_add_element($doc, $relocate, "dfrn:notify", $owner['notify']);
-	xml_add_element($doc, $relocate, "dfrn:poll", $owner['poll']);
-	xml_add_element($doc, $relocate, "dfrn:sitepubkey", get_config('system','site_pubkey'));
-
-	$root->appendChild($relocate);
-
-	return(trim($doc->saveXML()));
-}
-
-/**
- * @brief Adds the header elements for the DFRN protocol
- *
- * @param object $doc XML document
- * @param array $owner Owner record
- * @param string $authorelement Element name for the author
- * @param string $alternatelink link to profile or category
- * @param bool $public Is it a header for public posts?
- *
- * @return object XML root object
- */
-function dfrn_add_header($doc, $owner, $authorelement, $alternatelink = "", $public = false) {
-	$a = get_app();
-
-	if ($alternatelink == "")
-		$alternatelink = $owner['url'];
-
-	$root = $doc->createElementNS(NS_ATOM, 'feed');
-	$doc->appendChild($root);
-
-	$root->setAttribute("xmlns:thr", NS_THR);
-	$root->setAttribute("xmlns:at", "http://purl.org/atompub/tombstones/1.0");
-	$root->setAttribute("xmlns:media", NS_MEDIA);
-	$root->setAttribute("xmlns:dfrn", "http://purl.org/macgirvin/dfrn/1.0");
-	$root->setAttribute("xmlns:activity", NS_ACTIVITY);
-	$root->setAttribute("xmlns:georss", NS_GEORSS);
-	$root->setAttribute("xmlns:poco", NS_POCO);
-	$root->setAttribute("xmlns:ostatus", NS_OSTATUS);
-	$root->setAttribute("xmlns:statusnet", NS_STATUSNET);
-
-	xml_add_element($doc, $root, "id", $a->get_baseurl()."/profile/".$owner["nick"]);
-	xml_add_element($doc, $root, "title", $owner["name"]);
-
-	$attributes = array("uri" => "https://friendi.ca", "version" => FRIENDICA_VERSION."-".DB_UPDATE_VERSION);
-	xml_add_element($doc, $root, "generator", FRIENDICA_PLATFORM, $attributes);
-
-	$attributes = array("rel" => "license", "href" => "http://creativecommons.org/licenses/by/3.0/");
-	xml_add_element($doc, $root, "link", "", $attributes);
-
-	$attributes = array("rel" => "alternate", "type" => "text/html", "href" => $alternatelink);
-	xml_add_element($doc, $root, "link", "", $attributes);
-
-	ostatus_hublinks($doc, $root);
-
-	if ($public) {
-		$attributes = array("rel" => "salmon", "href" => $a->get_baseurl()."/salmon/".$owner["nick"]);
+		$attributes = array("rel" => "license", "href" => "http://creativecommons.org/licenses/by/3.0/");
 		xml_add_element($doc, $root, "link", "", $attributes);
 
-		$attributes = array("rel" => "http://salmon-protocol.org/ns/salmon-replies", "href" => $a->get_baseurl()."/salmon/".$owner["nick"]);
+		$attributes = array("rel" => "alternate", "type" => "text/html", "href" => $alternatelink);
 		xml_add_element($doc, $root, "link", "", $attributes);
 
-		$attributes = array("rel" => "http://salmon-protocol.org/ns/salmon-mention", "href" => $a->get_baseurl()."/salmon/".$owner["nick"]);
-		xml_add_element($doc, $root, "link", "", $attributes);
+		ostatus_hublinks($doc, $root);
+
+		if ($public) {
+			$attributes = array("rel" => "salmon", "href" => app::get_baseurl()."/salmon/".$owner["nick"]);
+			xml_add_element($doc, $root, "link", "", $attributes);
+
+			$attributes = array("rel" => "http://salmon-protocol.org/ns/salmon-replies", "href" => app::get_baseurl()."/salmon/".$owner["nick"]);
+			xml_add_element($doc, $root, "link", "", $attributes);
+
+			$attributes = array("rel" => "http://salmon-protocol.org/ns/salmon-mention", "href" => app::get_baseurl()."/salmon/".$owner["nick"]);
+			xml_add_element($doc, $root, "link", "", $attributes);
+		}
+
+		if ($owner['page-flags'] == PAGE_COMMUNITY)
+			xml_add_element($doc, $root, "dfrn:community", 1);
+
+		xml_add_element($doc, $root, "updated", datetime_convert("UTC", "UTC", "now", ATOM_TIME));
+
+		$author = self::add_author($doc, $owner, $authorelement, $public);
+		$root->appendChild($author);
+
+		return $root;
 	}
 
-	if ($owner['page-flags'] == PAGE_COMMUNITY)
-		xml_add_element($doc, $root, "dfrn:community", 1);
+	/**
+	 * @brief Adds the author element in the header for the DFRN protocol
+	 *
+	 * @param object $doc XML document
+	 * @param array $owner Owner record
+	 * @param string $authorelement Element name for the author
+	 *
+	 * @return object XML author object
+	 */
+	private function add_author($doc, $owner, $authorelement, $public) {
 
-	xml_add_element($doc, $root, "updated", datetime_convert("UTC", "UTC", "now", ATOM_TIME));
+		$author = $doc->createElement($authorelement);
 
-	$author = dfrn_add_author($doc, $owner, $authorelement, $public);
-	$root->appendChild($author);
+		$namdate = datetime_convert('UTC', 'UTC', $owner['name-date'].'+00:00' , ATOM_TIME);
+		$uridate = datetime_convert('UTC', 'UTC', $owner['uri-date'].'+00:00', ATOM_TIME);
+		$picdate = datetime_convert('UTC', 'UTC', $owner['avatar-date'].'+00:00', ATOM_TIME);
 
-	return $root;
-}
+		$attributes = array("dfrn:updated" => $namdate);
+		xml_add_element($doc, $author, "name", $owner["name"], $attributes);
 
-/**
- * @brief Adds the author element in the header for the DFRN protocol
- *
- * @param object $doc XML document
- * @param array $owner Owner record
- * @param string $authorelement Element name for the author
- *
- * @return object XML author object
- */
-function dfrn_add_author($doc, $owner, $authorelement, $public) {
-	$a = get_app();
+		$attributes = array("dfrn:updated" => $namdate);
+		xml_add_element($doc, $author, "uri", app::get_baseurl().'/profile/'.$owner["nickname"], $attributes);
 
-	$author = $doc->createElement($authorelement);
+		$attributes = array("rel" => "photo", "type" => "image/jpeg", "dfrn:updated" => $picdate,
+					"media:width" => 175, "media:height" => 175, "href" => $owner['photo']);
+		xml_add_element($doc, $author, "link", "", $attributes);
 
-	$namdate = datetime_convert('UTC', 'UTC', $owner['name-date'].'+00:00' , ATOM_TIME);
-	$uridate = datetime_convert('UTC', 'UTC', $owner['uri-date'].'+00:00', ATOM_TIME);
-	$picdate = datetime_convert('UTC', 'UTC', $owner['avatar-date'].'+00:00', ATOM_TIME);
+		$attributes = array("rel" => "avatar", "type" => "image/jpeg", "dfrn:updated" => $picdate,
+					"media:width" => 175, "media:height" => 175, "href" => $owner['photo']);
+		xml_add_element($doc, $author, "link", "", $attributes);
 
-	$attributes = array("dfrn:updated" => $namdate);
-	xml_add_element($doc, $author, "name", $owner["name"], $attributes);
+		$birthday = feed_birthday($owner['user_uid'], $owner['timezone']);
 
-	$attributes = array("dfrn:updated" => $namdate);
-	xml_add_element($doc, $author, "uri", $a->get_baseurl().'/profile/'.$owner["nickname"], $attributes);
+		if ($birthday)
+			xml_add_element($doc, $author, "dfrn:birthday", $birthday);
 
-	$attributes = array("rel" => "photo", "type" => "image/jpeg", "dfrn:updated" => $picdate,
-				"media:width" => 175, "media:height" => 175, "href" => $owner['photo']);
-	xml_add_element($doc, $author, "link", "", $attributes);
+		// The following fields will only be generated if this isn't for a public feed
+		if ($public)
+			return $author;
 
-	$attributes = array("rel" => "avatar", "type" => "image/jpeg", "dfrn:updated" => $picdate,
-				"media:width" => 175, "media:height" => 175, "href" => $owner['photo']);
-	xml_add_element($doc, $author, "link", "", $attributes);
+		// Only show contact details when we are allowed to
+		$r = q("SELECT `profile`.`about`, `profile`.`name`, `profile`.`homepage`, `user`.`nickname`, `user`.`timezone`,
+				`profile`.`locality`, `profile`.`region`, `profile`.`country-name`, `profile`.`pub_keywords`, `profile`.`dob`
+			FROM `profile`
+				INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
+				WHERE `profile`.`is-default` AND NOT `user`.`hidewall` AND `user`.`uid` = %d",
+			intval($owner['user_uid']));
+		if ($r) {
+			$profile = $r[0];
+			xml_add_element($doc, $author, "poco:displayName", $profile["name"]);
+			xml_add_element($doc, $author, "poco:updated", $namdate);
 
-	$birthday = feed_birthday($owner['user_uid'], $owner['timezone']);
+			if (trim($profile["dob"]) != "0000-00-00")
+				xml_add_element($doc, $author, "poco:birthday", "0000-".date("m-d", strtotime($profile["dob"])));
 
-	if ($birthday)
-		xml_add_element($doc, $author, "dfrn:birthday", $birthday);
+			xml_add_element($doc, $author, "poco:note", $profile["about"]);
+			xml_add_element($doc, $author, "poco:preferredUsername", $profile["nickname"]);
+
+			$savetz = date_default_timezone_get();
+			date_default_timezone_set($profile["timezone"]);
+			xml_add_element($doc, $author, "poco:utcOffset", date("P"));
+			date_default_timezone_set($savetz);
+
+			if (trim($profile["homepage"]) != "") {
+				$urls = $doc->createElement("poco:urls");
+				xml_add_element($doc, $urls, "poco:type", "homepage");
+				xml_add_element($doc, $urls, "poco:value", $profile["homepage"]);
+				xml_add_element($doc, $urls, "poco:primary", "true");
+				$author->appendChild($urls);
+			}
+
+			if (trim($profile["pub_keywords"]) != "") {
+				$keywords = explode(",", $profile["pub_keywords"]);
+
+				foreach ($keywords AS $keyword)
+					xml_add_element($doc, $author, "poco:tags", trim($keyword));
+
+			}
+
+			/// @todo When we are having the XMPP address in the profile we should propagate it here
+			$xmpp = "";
+			if (trim($xmpp) != "") {
+				$ims = $doc->createElement("poco:ims");
+				xml_add_element($doc, $ims, "poco:type", "xmpp");
+				xml_add_element($doc, $ims, "poco:value", $xmpp);
+				xml_add_element($doc, $ims, "poco:primary", "true");
+				$author->appendChild($ims);
+			}
+
+			if (trim($profile["locality"].$profile["region"].$profile["country-name"]) != "") {
+				$element = $doc->createElement("poco:address");
+
+				xml_add_element($doc, $element, "poco:formatted", formatted_location($profile));
+
+				if (trim($profile["locality"]) != "")
+					xml_add_element($doc, $element, "poco:locality", $profile["locality"]);
+
+				if (trim($profile["region"]) != "")
+					xml_add_element($doc, $element, "poco:region", $profile["region"]);
+
+				if (trim($profile["country-name"]) != "")
+					xml_add_element($doc, $element, "poco:country", $profile["country-name"]);
+
+				$author->appendChild($element);
+			}
+		}
 
-	// The following fields will only be generated if this isn't for a public feed
-	if ($public)
 		return $author;
+	}
 
-	// Only show contact details when we are allowed to
-	$r = q("SELECT `profile`.`about`, `profile`.`name`, `profile`.`homepage`, `user`.`nickname`, `user`.`timezone`,
-			`profile`.`locality`, `profile`.`region`, `profile`.`country-name`, `profile`.`pub_keywords`, `profile`.`dob`
-		FROM `profile`
-			INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
-			WHERE `profile`.`is-default` AND NOT `user`.`hidewall` AND `user`.`uid` = %d",
-		intval($owner['user_uid']));
-	if ($r) {
-		$profile = $r[0];
-		xml_add_element($doc, $author, "poco:displayName", $profile["name"]);
-		xml_add_element($doc, $author, "poco:updated", $namdate);
+	/**
+	 * @brief Adds the author elements in the "entry" elements of the DFRN protocol
+	 *
+	 * @param object $doc XML document
+	 * @param string $element Element name for the author
+	 * @param string $contact_url Link of the contact
+	 * @param array $items Item elements
+	 *
+	 * @return object XML author object
+	 */
+	private function add_entry_author($doc, $element, $contact_url, $item) {
 
-		if (trim($profile["dob"]) != "0000-00-00")
-			xml_add_element($doc, $author, "poco:birthday", "0000-".date("m-d", strtotime($profile["dob"])));
+		$contact = get_contact_details_by_url($contact_url, $item["uid"]);
 
-		xml_add_element($doc, $author, "poco:note", $profile["about"]);
-		xml_add_element($doc, $author, "poco:preferredUsername", $profile["nickname"]);
+		$author = $doc->createElement($element);
+		xml_add_element($doc, $author, "name", $contact["name"]);
+		xml_add_element($doc, $author, "uri", $contact["url"]);
 
-		$savetz = date_default_timezone_get();
-		date_default_timezone_set($profile["timezone"]);
-		xml_add_element($doc, $author, "poco:utcOffset", date("P"));
-		date_default_timezone_set($savetz);
+		/// @Todo
+		/// - Check real image type and image size
+		/// - Check which of these boths elements we should use
+		$attributes = array(
+				"rel" => "photo",
+				"type" => "image/jpeg",
+				"media:width" => 80,
+				"media:height" => 80,
+				"href" => $contact["photo"]);
+		xml_add_element($doc, $author, "link", "", $attributes);
 
-		if (trim($profile["homepage"]) != "") {
-			$urls = $doc->createElement("poco:urls");
-			xml_add_element($doc, $urls, "poco:type", "homepage");
-			xml_add_element($doc, $urls, "poco:value", $profile["homepage"]);
-			xml_add_element($doc, $urls, "poco:primary", "true");
-			$author->appendChild($urls);
+		$attributes = array(
+				"rel" => "avatar",
+				"type" => "image/jpeg",
+				"media:width" => 80,
+				"media:height" => 80,
+				"href" => $contact["photo"]);
+		xml_add_element($doc, $author, "link", "", $attributes);
+
+		return $author;
+	}
+
+	/**
+	 * @brief Adds the activity elements
+	 *
+	 * @param object $doc XML document
+	 * @param string $element Element name for the activity
+	 * @param string $activity activity value
+	 *
+	 * @return object XML activity object
+	 */
+	private function create_activity($doc, $element, $activity) {
+
+		if($activity) {
+			$entry = $doc->createElement($element);
+
+			$r = parse_xml_string($activity, false);
+			if(!$r)
+				return false;
+			if($r->type)
+				xml_add_element($doc, $entry, "activity:object-type", $r->type);
+			if($r->id)
+				xml_add_element($doc, $entry, "id", $r->id);
+			if($r->title)
+				xml_add_element($doc, $entry, "title", $r->title);
+			if($r->link) {
+				if(substr($r->link,0,1) === '<') {
+					if(strstr($r->link,'&') && (! strstr($r->link,'&amp;')))
+						$r->link = str_replace('&','&amp;', $r->link);
+
+					$r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
+
+					$data = parse_xml_string($r->link, false);
+					foreach ($data->attributes() AS $parameter => $value)
+						$attributes[$parameter] = $value;
+				} else
+					$attributes = array("rel" => "alternate", "type" => "text/html", "href" => $r->link);
+
+				xml_add_element($doc, $entry, "link", "", $attributes);
+			}
+			if($r->content)
+				xml_add_element($doc, $entry, "content", bbcode($r->content), array("type" => "html"));
+
+			return $entry;
 		}
 
-		if (trim($profile["pub_keywords"]) != "") {
-			$keywords = explode(",", $profile["pub_keywords"]);
+		return false;
+	}
 
-			foreach ($keywords AS $keyword)
-				xml_add_element($doc, $author, "poco:tags", trim($keyword));
+	/**
+	 * @brief Adds the elements for attachments
+	 *
+	 * @param object $doc XML document
+	 * @param object $root XML root
+	 * @param array $item Item element
+	 *
+	 * @return object XML attachment object
+	 */
+	private function get_attachment($doc, $root, $item) {
+		$arr = explode('[/attach],',$item['attach']);
+		if(count($arr)) {
+			foreach($arr as $r) {
+				$matches = false;
+				$cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
+				if($cnt) {
+					$attributes = array("rel" => "enclosure",
+							"href" => $matches[1],
+							"type" => $matches[3]);
 
-		}
+					if(intval($matches[2]))
+						$attributes["length"] = intval($matches[2]);
 
-		/// @todo When we are having the XMPP address in the profile we should propagate it here
-		$xmpp = "";
-		if (trim($xmpp) != "") {
-			$ims = $doc->createElement("poco:ims");
-			xml_add_element($doc, $ims, "poco:type", "xmpp");
-			xml_add_element($doc, $ims, "poco:value", $xmpp);
-			xml_add_element($doc, $ims, "poco:primary", "true");
-			$author->appendChild($ims);
-		}
+					if(trim($matches[4]) != "")
+						$attributes["title"] = trim($matches[4]);
 
-		if (trim($profile["locality"].$profile["region"].$profile["country-name"]) != "") {
-			$element = $doc->createElement("poco:address");
-
-			xml_add_element($doc, $element, "poco:formatted", formatted_location($profile));
-
-			if (trim($profile["locality"]) != "")
-				xml_add_element($doc, $element, "poco:locality", $profile["locality"]);
-
-			if (trim($profile["region"]) != "")
-				xml_add_element($doc, $element, "poco:region", $profile["region"]);
-
-			if (trim($profile["country-name"]) != "")
-				xml_add_element($doc, $element, "poco:country", $profile["country-name"]);
-
-			$author->appendChild($element);
+					xml_add_element($doc, $root, "link", "", $attributes);
+				}
+			}
 		}
 	}
 
-	return $author;
-}
+	/**
+	 * @brief Adds the "entry" elements for the DFRN protocol
+	 *
+	 * @param object $doc XML document
+	 * @param string $type "text" or "html"
+	 * @param array $item Item element
+	 * @param array $owner Owner record
+	 * @param bool $comment Trigger the sending of the "comment" element
+	 * @param int $cid Contact ID of the recipient
+	 *
+	 * @return object XML entry object
+	 */
+	private function entry($doc, $type, $item, $owner, $comment = false, $cid = 0) {
 
-/**
- * @brief Adds the author elements in the "entry" elements of the DFRN protocol
- *
- * @param object $doc XML document
- * @param string $element Element name for the author
- * @param string $contact_url Link of the contact
- * @param array $items Item elements
- *
- * @return object XML author object
- */
-function dfrn_add_entry_author($doc, $element, $contact_url, $item) {
+		$mentioned = array();
 
-	$contact = get_contact_details_by_url($contact_url, $item["uid"]);
+		if(!$item['parent'])
+			return;
 
-	$author = $doc->createElement($element);
-	xml_add_element($doc, $author, "name", $contact["name"]);
-	xml_add_element($doc, $author, "uri", $contact["url"]);
-
-	/// @Todo
-	/// - Check real image type and image size
-	/// - Check which of these boths elements we should use
-	$attributes = array(
-			"rel" => "photo",
-			"type" => "image/jpeg",
-			"media:width" => 80,
-			"media:height" => 80,
-			"href" => $contact["photo"]);
-	xml_add_element($doc, $author, "link", "", $attributes);
-
-	$attributes = array(
-			"rel" => "avatar",
-			"type" => "image/jpeg",
-			"media:width" => 80,
-			"media:height" => 80,
-			"href" => $contact["photo"]);
-	xml_add_element($doc, $author, "link", "", $attributes);
-
-	return $author;
-}
-
-/**
- * @brief Adds the activity elements
- *
- * @param object $doc XML document
- * @param string $element Element name for the activity
- * @param string $activity activity value
- *
- * @return object XML activity object
- */
-function dfrn_create_activity($doc, $element, $activity) {
-
-	if($activity) {
-		$entry = $doc->createElement($element);
-
-		$r = parse_xml_string($activity, false);
-		if(!$r)
-			return false;
-		if($r->type)
-			xml_add_element($doc, $entry, "activity:object-type", $r->type);
-		if($r->id)
-			xml_add_element($doc, $entry, "id", $r->id);
-		if($r->title)
-			xml_add_element($doc, $entry, "title", $r->title);
-		if($r->link) {
-			if(substr($r->link,0,1) === '<') {
-				if(strstr($r->link,'&') && (! strstr($r->link,'&amp;')))
-					$r->link = str_replace('&','&amp;', $r->link);
-
-				$r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
-
-				$data = parse_xml_string($r->link, false);
-				foreach ($data->attributes() AS $parameter => $value)
-					$attributes[$parameter] = $value;
-			} else
-				$attributes = array("rel" => "alternate", "type" => "text/html", "href" => $r->link);
-
-			xml_add_element($doc, $entry, "link", "", $attributes);
+		if($item['deleted']) {
+			$attributes = array("ref" => $item['uri'], "when" => datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME));
+			return xml_create_element($doc, "at:deleted-entry", "", $attributes);
 		}
-		if($r->content)
-			xml_add_element($doc, $entry, "content", bbcode($r->content), array("type" => "html"));
+
+		$entry = $doc->createElement("entry");
+
+		if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
+			$body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
+		else
+			$body = $item['body'];
+
+		if ($type == 'html') {
+			$htmlbody = $body;
+
+			if ($item['title'] != "")
+				$htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
+
+			$htmlbody = bbcode($htmlbody, false, false, 7);
+		}
+
+		$author = self::add_entry_author($doc, "author", $item["author-link"], $item);
+		$entry->appendChild($author);
+
+		$dfrnowner = self::add_entry_author($doc, "dfrn:owner", $item["owner-link"], $item);
+		$entry->appendChild($dfrnowner);
+
+		if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
+			$parent = q("SELECT `guid` FROM `item` WHERE `id` = %d", intval($item["parent"]));
+			$parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
+			$attributes = array("ref" => $parent_item, "type" => "text/html", "href" => app::get_baseurl().'/display/'.$parent[0]['guid']);
+			xml_add_element($doc, $entry, "thr:in-reply-to", "", $attributes);
+		}
+
+		xml_add_element($doc, $entry, "id", $item["uri"]);
+		xml_add_element($doc, $entry, "title", $item["title"]);
+
+		xml_add_element($doc, $entry, "published", datetime_convert("UTC","UTC",$item["created"]."+00:00",ATOM_TIME));
+		xml_add_element($doc, $entry, "updated", datetime_convert("UTC","UTC",$item["edited"]."+00:00",ATOM_TIME));
+
+		xml_add_element($doc, $entry, "dfrn:env", base64url_encode($body, true));
+		xml_add_element($doc, $entry, "content", (($type === 'html') ? $htmlbody : $body), array("type" => $type));
+
+		xml_add_element($doc, $entry, "link", "", array("rel" => "alternate", "type" => "text/html",
+								"href" => app::get_baseurl()."/display/".$item["guid"]));
+
+		// "comment-allow" is some old fashioned stuff for old Friendica versions.
+		// It is included in the rewritten code for completeness
+		if ($comment)
+			xml_add_element($doc, $entry, "dfrn:comment-allow", intval($item['last-child']));
+
+		if($item['location'])
+			xml_add_element($doc, $entry, "dfrn:location", $item['location']);
+
+		if($item['coord'])
+			xml_add_element($doc, $entry, "georss:point", $item['coord']);
+
+		if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
+			xml_add_element($doc, $entry, "dfrn:private", (($item['private']) ? $item['private'] : 1));
+
+		if($item['extid'])
+			xml_add_element($doc, $entry, "dfrn:extid", $item['extid']);
+
+		if($item['bookmark'])
+			xml_add_element($doc, $entry, "dfrn:bookmark", "true");
+
+		if($item['app'])
+			xml_add_element($doc, $entry, "statusnet:notice_info", "", array("local_id" => $item['id'], "source" => $item['app']));
+
+		xml_add_element($doc, $entry, "dfrn:diaspora_guid", $item["guid"]);
+
+		// The signed text contains the content in Markdown, the sender handle and the signatur for the content
+		// It is needed for relayed comments to Diaspora.
+		if($item['signed_text']) {
+			$sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
+			xml_add_element($doc, $entry, "dfrn:diaspora_signature", $sign);
+		}
+
+		xml_add_element($doc, $entry, "activity:verb", construct_verb($item));
+
+		$actobj = self::create_activity($doc, "activity:object", $item['object']);
+		if ($actobj)
+			$entry->appendChild($actobj);
+
+		$actarg = self::create_activity($doc, "activity:target", $item['target']);
+		if ($actarg)
+			$entry->appendChild($actarg);
+
+		$tags = item_getfeedtags($item);
+
+		if(count($tags)) {
+			foreach($tags as $t)
+				if (($type != 'html') OR ($t[0] != "@"))
+					xml_add_element($doc, $entry, "category", "", array("scheme" => "X-DFRN:".$t[0].":".$t[1], "term" => $t[2]));
+		}
+
+		if(count($tags))
+			foreach($tags as $t)
+				if ($t[0] == "@")
+					$mentioned[$t[1]] = $t[1];
+
+		foreach ($mentioned AS $mention) {
+			$r = q("SELECT `forum`, `prv` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s'",
+				intval($owner["uid"]),
+				dbesc(normalise_link($mention)));
+			if ($r[0]["forum"] OR $r[0]["prv"])
+				xml_add_element($doc, $entry, "link", "", array("rel" => "mentioned",
+											"ostatus:object-type" => ACTIVITY_OBJ_GROUP,
+											"href" => $mention));
+			else
+				xml_add_element($doc, $entry, "link", "", array("rel" => "mentioned",
+											"ostatus:object-type" => ACTIVITY_OBJ_PERSON,
+											"href" => $mention));
+		}
+
+		self::get_attachment($doc, $entry, $item);
 
 		return $entry;
 	}
 
-	return false;
-}
+	/**
+	 * @brief Delivers the atom content to the contacts
+	 *
+	 * @param array $owner Owner record
+	 * @param array $contactr Contact record of the receiver
+	 * @param string $atom Content that will be transmitted
+	 * @param bool $dissolve (to be documented)
+	 *
+	 * @return int Deliver status. -1 means an error.
+	 */
+	function deliver($owner,$contact,$atom, $dissolve = false) {
 
-/**
- * @brief Adds the elements for attachments
- *
- * @param object $doc XML document
- * @param object $root XML root
- * @param array $item Item element
- *
- * @return object XML attachment object
- */
-function dfrn_get_attachment($doc, $root, $item) {
-	$arr = explode('[/attach],',$item['attach']);
-	if(count($arr)) {
-		foreach($arr as $r) {
-			$matches = false;
-			$cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
-			if($cnt) {
-				$attributes = array("rel" => "enclosure",
-						"href" => $matches[1],
-						"type" => $matches[3]);
+		$a = get_app();
 
-				if(intval($matches[2]))
-					$attributes["length"] = intval($matches[2]);
+		$idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
 
-				if(trim($matches[4]) != "")
-					$attributes["title"] = trim($matches[4]);
+		if($contact['duplex'] && $contact['dfrn-id'])
+			$idtosend = '0:' . $orig_id;
+		if($contact['duplex'] && $contact['issued-id'])
+			$idtosend = '1:' . $orig_id;
 
-				xml_add_element($doc, $root, "link", "", $attributes);
+
+		$rino = get_config('system','rino_encrypt');
+		$rino = intval($rino);
+		// use RINO1 if mcrypt isn't installed and RINO2 was selected
+		if ($rino==2 and !function_exists('mcrypt_create_iv')) $rino=1;
+
+		logger("Local rino version: ". $rino, LOGGER_DEBUG);
+
+		$ssl_val = intval(get_config('system','ssl_policy'));
+		$ssl_policy = '';
+
+		switch($ssl_val){
+			case SSL_POLICY_FULL:
+				$ssl_policy = 'full';
+				break;
+			case SSL_POLICY_SELFSIGN:
+				$ssl_policy = 'self';
+				break;
+			case SSL_POLICY_NONE:
+			default:
+				$ssl_policy = 'none';
+				break;
+		}
+
+		$url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino='.$rino : '');
+
+		logger('dfrn_deliver: ' . $url);
+
+		$xml = fetch_url($url);
+
+		$curl_stat = $a->get_curl_code();
+		if(! $curl_stat)
+			return(-1); // timed out
+
+		logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
+
+		if(! $xml)
+			return 3;
+
+		if(strpos($xml,'<?xml') === false) {
+			logger('dfrn_deliver: no valid XML returned');
+			logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
+			return 3;
+		}
+
+		$res = parse_xml_string($xml);
+
+		if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
+			return (($res->status) ? $res->status : 3);
+
+		$postvars     = array();
+		$sent_dfrn_id = hex2bin((string) $res->dfrn_id);
+		$challenge    = hex2bin((string) $res->challenge);
+		$perm         = (($res->perm) ? $res->perm : null);
+		$dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
+		$rino_remote_version = intval($res->rino);
+		$page         = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
+
+		logger("Remote rino version: ".$rino_remote_version." for ".$contact["url"], LOGGER_DEBUG);
+
+		if($owner['page-flags'] == PAGE_PRVGROUP)
+			$page = 2;
+
+		$final_dfrn_id = '';
+
+		if($perm) {
+			if((($perm == 'rw') && (! intval($contact['writable'])))
+				|| (($perm == 'r') && (intval($contact['writable'])))) {
+				q("update contact set writable = %d where id = %d",
+					intval(($perm == 'rw') ? 1 : 0),
+					intval($contact['id'])
+				);
+				$contact['writable'] = (string) 1 - intval($contact['writable']);
 			}
 		}
+
+		if(($contact['duplex'] && strlen($contact['pubkey']))
+			|| ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
+			|| ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
+			openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
+			openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
+		} else {
+			openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
+			openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
+		}
+
+		$final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
+
+		if(strpos($final_dfrn_id,':') == 1)
+			$final_dfrn_id = substr($final_dfrn_id,2);
+
+		if($final_dfrn_id != $orig_id) {
+			logger('dfrn_deliver: wrong dfrn_id.');
+			// did not decode properly - cannot trust this site
+			return 3;
+		}
+
+		$postvars['dfrn_id']      = $idtosend;
+		$postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
+		if($dissolve)
+			$postvars['dissolve'] = '1';
+
+
+		if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
+			$postvars['data'] = $atom;
+			$postvars['perm'] = 'rw';
+		} else {
+			$postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
+			$postvars['perm'] = 'r';
+		}
+
+		$postvars['ssl_policy'] = $ssl_policy;
+
+		if($page)
+			$postvars['page'] = $page;
+
+
+		if($rino>0 && $rino_remote_version>0 && (! $dissolve)) {
+			logger('rino version: '. $rino_remote_version);
+
+			switch($rino_remote_version) {
+				case 1:
+					// Deprecated rino version!
+					$key = substr(random_string(),0,16);
+					$data = aes_encrypt($postvars['data'],$key);
+					break;
+				case 2:
+					// RINO 2 based on php-encryption
+					try {
+						$key = Crypto::createNewRandomKey();
+					} catch (CryptoTestFailed $ex) {
+						logger('Cannot safely create a key');
+						return -1;
+					} catch (CannotPerformOperation $ex) {
+						logger('Cannot safely create a key');
+						return -1;
+					}
+					try {
+						$data = Crypto::encrypt($postvars['data'], $key);
+					} catch (CryptoTestFailed $ex) {
+						logger('Cannot safely perform encryption');
+						return -1;
+					} catch (CannotPerformOperation $ex) {
+						logger('Cannot safely perform encryption');
+						return -1;
+					}
+					break;
+				default:
+					logger("rino: invalid requested verision '$rino_remote_version'");
+					return -1;
+			}
+
+			$postvars['rino'] = $rino_remote_version;
+			$postvars['data'] = bin2hex($data);
+
+			#logger('rino: sent key = ' . $key, LOGGER_DEBUG);
+
+
+			if($dfrn_version >= 2.1) {
+				if(($contact['duplex'] && strlen($contact['pubkey']))
+					|| ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
+					|| ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey'])))
+
+					openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
+				else
+					openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
+
+			} else {
+				if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY))
+					openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
+				else
+					openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
+
+			}
+
+			logger('md5 rawkey ' . md5($postvars['key']));
+
+			$postvars['key'] = bin2hex($postvars['key']);
+		}
+
+
+		logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
+
+		$xml = post_url($contact['notify'],$postvars);
+
+		logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
+
+		$curl_stat = $a->get_curl_code();
+		if((! $curl_stat) || (! strlen($xml)))
+			return(-1); // timed out
+
+		if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
+			return(-1);
+
+		if(strpos($xml,'<?xml') === false) {
+			logger('dfrn_deliver: phase 2: no valid XML returned');
+			logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
+			return 3;
+		}
+
+		if($contact['term-date'] != '0000-00-00 00:00:00') {
+			logger("dfrn_deliver: $url back from the dead - removing mark for death");
+			require_once('include/Contact.php');
+			unmark_for_death($contact);
+		}
+
+		$res = parse_xml_string($xml);
+
+		return $res->status;
 	}
 }
-
-/**
- * @brief Adds the "entry" elements for the DFRN protocol
- *
- * @param object $doc XML document
- * @param string $type "text" or "html"
- * @param array $item Item element
- * @param array $owner Owner record
- * @param bool $comment Trigger the sending of the "comment" element
- * @param int $cid Contact ID of the recipient
- *
- * @return object XML entry object
- */
-function dfrn_entry($doc, $type, $item, $owner, $comment = false, $cid = 0) {
-	$a = get_app();
-
-	$mentioned = array();
-
-	if(!$item['parent'])
-		return;
-
-	if($item['deleted']) {
-		$attributes = array("ref" => $item['uri'], "when" => datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME));
-		return xml_create_element($doc, "at:deleted-entry", "", $attributes);
-	}
-
-	$entry = $doc->createElement("entry");
-
-	if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
-		$body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
-	else
-		$body = $item['body'];
-
-	if ($type == 'html') {
-		$htmlbody = $body;
-
-		if ($item['title'] != "")
-			$htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
-
-		$htmlbody = bbcode($htmlbody, false, false, 7);
-	}
-
-	$author = dfrn_add_entry_author($doc, "author", $item["author-link"], $item);
-	$entry->appendChild($author);
-
-	$dfrnowner = dfrn_add_entry_author($doc, "dfrn:owner", $item["owner-link"], $item);
-	$entry->appendChild($dfrnowner);
-
-	if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
-		$parent = q("SELECT `guid` FROM `item` WHERE `id` = %d", intval($item["parent"]));
-		$parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
-		$attributes = array("ref" => $parent_item, "type" => "text/html", "href" => $a->get_baseurl().'/display/'.$parent[0]['guid']);
-		xml_add_element($doc, $entry, "thr:in-reply-to", "", $attributes);
-	}
-
-	xml_add_element($doc, $entry, "id", $item["uri"]);
-	xml_add_element($doc, $entry, "title", $item["title"]);
-
-	xml_add_element($doc, $entry, "published", datetime_convert("UTC","UTC",$item["created"]."+00:00",ATOM_TIME));
-	xml_add_element($doc, $entry, "updated", datetime_convert("UTC","UTC",$item["edited"]."+00:00",ATOM_TIME));
-
-	xml_add_element($doc, $entry, "dfrn:env", base64url_encode($body, true));
-	xml_add_element($doc, $entry, "content", (($type === 'html') ? $htmlbody : $body), array("type" => $type));
-
-	xml_add_element($doc, $entry, "link", "", array("rel" => "alternate", "type" => "text/html",
-							"href" => $a->get_baseurl()."/display/".$item["guid"]));
-
-	// "comment-allow" is some old fashioned stuff for old Friendica versions.
-	// It is included in the rewritten code for completeness
-	if ($comment)
-		xml_add_element($doc, $entry, "dfrn:comment-allow", intval($item['last-child']));
-
-	if($item['location'])
-		xml_add_element($doc, $entry, "dfrn:location", $item['location']);
-
-	if($item['coord'])
-		xml_add_element($doc, $entry, "georss:point", $item['coord']);
-
-	if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
-		xml_add_element($doc, $entry, "dfrn:private", (($item['private']) ? $item['private'] : 1));
-
-	if($item['extid'])
-		xml_add_element($doc, $entry, "dfrn:extid", $item['extid']);
-
-	if($item['bookmark'])
-		xml_add_element($doc, $entry, "dfrn:bookmark", "true");
-
-	if($item['app'])
-		xml_add_element($doc, $entry, "statusnet:notice_info", "", array("local_id" => $item['id'], "source" => $item['app']));
-
-	xml_add_element($doc, $entry, "dfrn:diaspora_guid", $item["guid"]);
-
-	// The signed text contains the content in Markdown, the sender handle and the signatur for the content
-	// It is needed for relayed comments to Diaspora.
-	if($item['signed_text']) {
-		$sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
-		xml_add_element($doc, $entry, "dfrn:diaspora_signature", $sign);
-	}
-
-	xml_add_element($doc, $entry, "activity:verb", construct_verb($item));
-
-	$actobj = dfrn_create_activity($doc, "activity:object", $item['object']);
-	if ($actobj)
-		$entry->appendChild($actobj);
-
-	$actarg = dfrn_create_activity($doc, "activity:target", $item['target']);
-	if ($actarg)
-		$entry->appendChild($actarg);
-
-	$tags = item_getfeedtags($item);
-
-	if(count($tags)) {
-		foreach($tags as $t)
-			if (($type != 'html') OR ($t[0] != "@"))
-				xml_add_element($doc, $entry, "category", "", array("scheme" => "X-DFRN:".$t[0].":".$t[1], "term" => $t[2]));
-	}
-
-	if(count($tags))
-		foreach($tags as $t)
-			if ($t[0] == "@")
-				$mentioned[$t[1]] = $t[1];
-
-	foreach ($mentioned AS $mention) {
-		$r = q("SELECT `forum`, `prv` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s'",
-			intval($owner["uid"]),
-			dbesc(normalise_link($mention)));
-		if ($r[0]["forum"] OR $r[0]["prv"])
-			xml_add_element($doc, $entry, "link", "", array("rel" => "mentioned",
-										"ostatus:object-type" => ACTIVITY_OBJ_GROUP,
-										"href" => $mention));
-		else
-			xml_add_element($doc, $entry, "link", "", array("rel" => "mentioned",
-										"ostatus:object-type" => ACTIVITY_OBJ_PERSON,
-										"href" => $mention));
-	}
-
-	dfrn_get_attachment($doc, $entry, $item);
-
-	return $entry;
-}
diff --git a/include/items.php b/include/items.php
index 6d3cd2b7c7..cf044d8837 100644
--- a/include/items.php
+++ b/include/items.php
@@ -1696,245 +1696,9 @@ function tgroup_check($uid,$item) {
 	if((! $community_page) && (! $prvgroup))
 		return false;
 
-
-
 	return true;
-
 }
 
-
-
-
-
-
-function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
-
-	$a = get_app();
-
-	$idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
-
-	if($contact['duplex'] && $contact['dfrn-id'])
-		$idtosend = '0:' . $orig_id;
-	if($contact['duplex'] && $contact['issued-id'])
-		$idtosend = '1:' . $orig_id;
-
-
-	$rino = get_config('system','rino_encrypt');
-	$rino = intval($rino);
-	// use RINO1 if mcrypt isn't installed and RINO2 was selected
-	if ($rino==2 and !function_exists('mcrypt_create_iv')) $rino=1;
-
-	logger("Local rino version: ". $rino, LOGGER_DEBUG);
-
-	$ssl_val = intval(get_config('system','ssl_policy'));
-	$ssl_policy = '';
-
-	switch($ssl_val){
-		case SSL_POLICY_FULL:
-			$ssl_policy = 'full';
-			break;
-		case SSL_POLICY_SELFSIGN:
-			$ssl_policy = 'self';
-			break;
-		case SSL_POLICY_NONE:
-		default:
-			$ssl_policy = 'none';
-			break;
-	}
-
-	$url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino='.$rino : '');
-
-	logger('dfrn_deliver: ' . $url);
-
-	$xml = fetch_url($url);
-
-	$curl_stat = $a->get_curl_code();
-	if(! $curl_stat)
-		return(-1); // timed out
-
-	logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
-
-	if(! $xml)
-		return 3;
-
-	if(strpos($xml,'<?xml') === false) {
-		logger('dfrn_deliver: no valid XML returned');
-		logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
-		return 3;
-	}
-
-	$res = parse_xml_string($xml);
-
-	if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
-		return (($res->status) ? $res->status : 3);
-
-	$postvars     = array();
-	$sent_dfrn_id = hex2bin((string) $res->dfrn_id);
-	$challenge    = hex2bin((string) $res->challenge);
-	$perm         = (($res->perm) ? $res->perm : null);
-	$dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
-	$rino_remote_version = intval($res->rino);
-	$page         = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
-
-	logger("Remote rino version: ".$rino_remote_version." for ".$contact["url"], LOGGER_DEBUG);
-
-	if($owner['page-flags'] == PAGE_PRVGROUP)
-		$page = 2;
-
-	$final_dfrn_id = '';
-
-	if($perm) {
-		if((($perm == 'rw') && (! intval($contact['writable'])))
-		|| (($perm == 'r') && (intval($contact['writable'])))) {
-			q("update contact set writable = %d where id = %d",
-				intval(($perm == 'rw') ? 1 : 0),
-				intval($contact['id'])
-			);
-			$contact['writable'] = (string) 1 - intval($contact['writable']);
-		}
-	}
-
-	if(($contact['duplex'] && strlen($contact['pubkey']))
-		|| ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
-		|| ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
-		openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
-		openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
-	}
-	else {
-		openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
-		openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
-	}
-
-	$final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
-
-	if(strpos($final_dfrn_id,':') == 1)
-		$final_dfrn_id = substr($final_dfrn_id,2);
-
-	if($final_dfrn_id != $orig_id) {
-		logger('dfrn_deliver: wrong dfrn_id.');
-		// did not decode properly - cannot trust this site
-		return 3;
-	}
-
-	$postvars['dfrn_id']      = $idtosend;
-	$postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
-	if($dissolve)
-		$postvars['dissolve'] = '1';
-
-
-	if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
-		$postvars['data'] = $atom;
-		$postvars['perm'] = 'rw';
-	}
-	else {
-		$postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
-		$postvars['perm'] = 'r';
-	}
-
-	$postvars['ssl_policy'] = $ssl_policy;
-
-	if($page)
-		$postvars['page'] = $page;
-
-
-	if($rino>0 && $rino_remote_version>0 && (! $dissolve)) {
-		logger('rino version: '. $rino_remote_version);
-
-		switch($rino_remote_version) {
-			case 1:
-				// Deprecated rino version!
-				$key = substr(random_string(),0,16);
-				$data = aes_encrypt($postvars['data'],$key);
-				break;
-			case 2:
-				// RINO 2 based on php-encryption
-				try {
-					$key = Crypto::createNewRandomKey();
-				} catch (CryptoTestFailed $ex) {
-					logger('Cannot safely create a key');
-					return -1;
-				} catch (CannotPerformOperation $ex) {
-					logger('Cannot safely create a key');
-					return -1;
-				}
-				try {
-					$data = Crypto::encrypt($postvars['data'], $key);
-				} catch (CryptoTestFailed $ex) {
-					logger('Cannot safely perform encryption');
-					return -1;
-				} catch (CannotPerformOperation $ex) {
-					logger('Cannot safely perform encryption');
-					return -1;
-				}
-				break;
-			default:
-				logger("rino: invalid requested verision '$rino_remote_version'");
-				return -1;
-		}
-
-		$postvars['rino'] = $rino_remote_version;
-		$postvars['data'] = bin2hex($data);
-
-		#logger('rino: sent key = ' . $key, LOGGER_DEBUG);
-
-
-		if($dfrn_version >= 2.1) {
-			if(($contact['duplex'] && strlen($contact['pubkey']))
-				|| ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
-				|| ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
-
-				openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
-			}
-			else {
-				openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
-			}
-		}
-		else {
-			if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
-				openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
-			}
-			else {
-				openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
-			}
-		}
-
-		logger('md5 rawkey ' . md5($postvars['key']));
-
-		$postvars['key'] = bin2hex($postvars['key']);
-	}
-
-
-	logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
-
-	$xml = post_url($contact['notify'],$postvars);
-
-	logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
-
-	$curl_stat = $a->get_curl_code();
-	if((! $curl_stat) || (! strlen($xml)))
-		return(-1); // timed out
-
-	if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
-		return(-1);
-
-	if(strpos($xml,'<?xml') === false) {
-		logger('dfrn_deliver: phase 2: no valid XML returned');
-		logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
-		return 3;
-	}
-
-	if($contact['term-date'] != '0000-00-00 00:00:00') {
-		logger("dfrn_deliver: $url back from the dead - removing mark for death");
-		require_once('include/Contact.php');
-		unmark_for_death($contact);
-	}
-
-	$res = parse_xml_string($xml);
-
-	return $res->status;
-}
-
-
 /*
   This function returns true if $update has an edited timestamp newer
   than $existing, i.e. $update contains new data which should override
diff --git a/include/queue.php b/include/queue.php
index f40768f0d0..1525ca3abf 100644
--- a/include/queue.php
+++ b/include/queue.php
@@ -1,6 +1,7 @@
 <?php
 require_once("boot.php");
 require_once('include/queue_fn.php');
+require_once('include/dfrn.php');
 
 function queue_run(&$argv, &$argc){
 	global $a, $db;
@@ -179,7 +180,7 @@ function queue_run(&$argv, &$argc){
 		switch($contact['network']) {
 			case NETWORK_DFRN:
 				logger('queue: dfrndelivery: item '.$q_item['id'].' for '.$contact['name'].' <'.$contact['url'].'>');
-				$deliver_status = dfrn_deliver($owner,$contact,$data);
+				$deliver_status = dfrn::deliver($owner,$contact,$data);
 
 				if($deliver_status == (-1)) {
 					update_queue_time($q_item['id']);
diff --git a/mod/dfrn_poll.php b/mod/dfrn_poll.php
index 658a248c62..ab6637607e 100644
--- a/mod/dfrn_poll.php
+++ b/mod/dfrn_poll.php
@@ -44,7 +44,7 @@ function dfrn_poll_init(&$a) {
 
 		logger('dfrn_poll: public feed request from ' . $_SERVER['REMOTE_ADDR'] . ' for ' . $user);
 		header("Content-type: application/atom+xml");
-		echo dfrn_feed($a, '', $user,$last_update);
+		echo dfrn::feed('', $user,$last_update);
 		killme();
 	}
 
@@ -371,7 +371,7 @@ function dfrn_poll_post(&$a) {
 		}
 
 		header("Content-type: application/atom+xml");
-		$o = dfrn_feed($a,$dfrn_id, $a->argv[1], $last_update, $direction);
+		$o = dfrn::feed($dfrn_id, $a->argv[1], $last_update, $direction);
 		echo $o;
 		killme();