= 3) ? '' : '/') . (($achar === '?') ? '?f=&' : '&') . 'zid=' . urlencode($myaddr);
	else
		$zurl = $s;
	// put fragment at the end
	if($fragment)
		$zurl .= '#' . $fragment;
	$arr = [
		'url' => $s,
		'zid' => urlencode($myaddr),
		'result' => $zurl
	];
	/**
	 * @hooks zid
	 *   Called when adding the observer's zid to a URL.
	 *   * \e string \b url - url to accept zid
	 *   * \e string \b zid - urlencoded zid
	 *   * \e string \b result - the return string we calculated, change it if you want to return something else
	 */
	call_hooks('zid', $arr);
	return $arr['result'];
}
function strip_query_param($s,$param) {
	return preg_replace('/[\?&]' . $param . '=(.*?)(&|$)/ism','$2',$s);
}
function strip_zids($s) {
	return preg_replace('/[\?&]zid=(.*?)(&|$)/ism','$2',$s);
}
function strip_owt($s) {
	return preg_replace('/[\?&]owt=(.*?)(&|$)/ism','$2',$s);
}
function strip_zats($s) {
	return preg_replace('/[\?&]zat=(.*?)(&|$)/ism','$2',$s);
}
function strip_escaped_zids($s) {
	$x = preg_replace('/&\;zid=(.*?)(&|$)/ism','$2',$s);
	return strip_query_param($x,'f');
}
function clean_query_string($s = '') {
	$x = strip_zids(($s) ? $s : \App::$query_string);
	$x = strip_owt($x);
	$x = strip_zats($x);
	$x = strip_query_param($x,'sort');
	return strip_query_param($x,'f');
}
/**
 * zidify_callback() and zidify_links() work together to turn any HTML a tags with class="zrl" into zid links
 * These will typically be generated by a bbcode '[zrl]' tag. This is done inside prepare_text() rather than bbcode()
 * because the latter is used for general purpose conversions and the former is used only when preparing text for
 * immediate display.
 *
 * @TODO Issues: Currently the order of HTML parameters in the text is somewhat rigid and inflexible.
 *    We assume it looks like \ and will not work if zrl and href appear in a different order.
 *
 * @param array $match
 * @return string
 */
function zidify_callback($match) {
	$arr = [ 'zid' => ((strpos($match[1],'zrl')) ? true : false), 'url' => $match[2] ];
	call_hooks('zidify', $arr);
	$replace = ' ((strpos($match[1],'zrl')) ? true : false), 'url' => $match[2] ];
	call_hooks('zidify', $arr);
	$replace = ' ? zid($arr['url']) : $arr['url']) . ') $hubloc,
			'url' => \App::$query_string,
			'session' => $_SESSION
	];
	/**
	 * @hooks magic_auth_success
	 *   Called when a magic-auth was successful.
	 *   * \e array \b xchan
	 *   * \e string \b url
	 *   * \e array \b session
	 */
	call_hooks('magic_auth_success', $arr);
	\App::set_observer($hubloc);
	require_once('include/security.php');
	\App::set_groups(init_groups_visitor($_SESSION['visitor_id']));
	if(! get_config('system', 'hide_owa_greeting'))
		info(sprintf( t('OpenWebAuth: %1$s welcomes %2$s'),\App::get_hostname(), $hubloc['xchan_name']));
	logger('OpenWebAuth: auth success from ' . $hubloc['xchan_addr']);
}
function observer_auth($ob_hash) {
	if($ob_hash === false) {
		return;
	}
	$r = q("select * from hubloc left join xchan on xchan_hash = hubloc_hash
		where hubloc_addr = '%s' or hubloc_id_url = '%s' or hubloc_hash = '%s' order by hubloc_id desc",
		dbesc($ob_hash),
		dbesc($ob_hash),
		dbesc($ob_hash)
	);
	if(! $r) {
		// finger them if they can't be found.
		$wf = discover_by_webbie($ob_hash);
		if($wf) {
			$r = q("select * from hubloc left join xchan on xchan_hash = hubloc_hash
				where hubloc_addr = '%s' or hubloc_id_url = '%s' or hubloc_hash = '%s' order by hubloc_id desc",
				dbesc($ob_hash),
				dbesc($ob_hash),
				dbesc($ob_hash)
			);
		}
	}
	if(! $r) {
		logger('unable to finger ' . $ob_hash);
		return;
	}
	$hubloc = $r[0];
	$_SESSION['authenticated'] = 1;
	// normal visitor (remote_channel) login session credentials
	$_SESSION['visitor_id'] = $hubloc['xchan_hash'];
	$_SESSION['my_url'] = $hubloc['xchan_url'];
	$_SESSION['my_address'] = $hubloc['hubloc_addr'];
	$_SESSION['remote_hub'] = $hubloc['hubloc_url'];
	$_SESSION['DNT'] = 1;
	\App::set_observer($hubloc);
	require_once('include/security.php');
	\App::set_groups(init_groups_visitor($_SESSION['visitor_id']));
}
 $hubloc,
			'url' => \App::$query_string,
			'session' => $_SESSION
	];
	/**
	 * @hooks magic_auth_success
	 *   Called when a magic-auth was successful.
	 *   * \e array \b xchan
	 *   * \e string \b url
	 *   * \e array \b session
	 */
	call_hooks('magic_auth_success', $arr);
	\App::set_observer($hubloc);
	require_once('include/security.php');
	\App::set_groups(init_groups_visitor($_SESSION['visitor_id']));
	if(! get_config('system', 'hide_owa_greeting'))
		info(sprintf( t('OpenWebAuth: %1$s welcomes %2$s'),\App::get_hostname(), $hubloc['xchan_name']));
	logger('OpenWebAuth: auth success from ' . $hubloc['xchan_addr']);
}
function observer_auth($ob_hash) {
	if($ob_hash === false) {
		return;
	}
	$r = q("select * from hubloc left join xchan on xchan_hash = hubloc_hash
		where hubloc_addr = '%s' or hubloc_id_url = '%s' or hubloc_hash = '%s' order by hubloc_id desc",
		dbesc($ob_hash),
		dbesc($ob_hash),
		dbesc($ob_hash)
	);
	if(! $r) {
		// finger them if they can't be found.
		$wf = discover_by_webbie($ob_hash);
		if($wf) {
			$r = q("select * from hubloc left join xchan on xchan_hash = hubloc_hash
				where hubloc_addr = '%s' or hubloc_id_url = '%s' or hubloc_hash = '%s' order by hubloc_id desc",
				dbesc($ob_hash),
				dbesc($ob_hash),
				dbesc($ob_hash)
			);
		}
	}
	if(! $r) {
		logger('unable to finger ' . $ob_hash);
		return;
	}
	$hubloc = $r[0];
	$_SESSION['authenticated'] = 1;
	// normal visitor (remote_channel) login session credentials
	$_SESSION['visitor_id'] = $hubloc['xchan_hash'];
	$_SESSION['my_url'] = $hubloc['xchan_url'];
	$_SESSION['my_address'] = $hubloc['hubloc_addr'];
	$_SESSION['remote_hub'] = $hubloc['hubloc_url'];
	$_SESSION['DNT'] = 1;
	\App::set_observer($hubloc);
	require_once('include/security.php');
	\App::set_groups(init_groups_visitor($_SESSION['visitor_id']));
}