<?php
/**
 * This file implements misc functions that handle output of the HTML page.
 *
 * This file is part of the evoCore framework - {@link http://evocore.net/}
 * See also {@link https://github.com/b2evolution/b2evolution}.
 *
 * @license GNU GPL v2 - {@link http://b2evolution.net/about/gnu-gpl-license}
 *
 * @copyright (c)2003-2020 by Francois Planque - {@link http://fplanque.com/}
 * Parts of this file are copyright (c)2004-2006 by Daniel HAHLER - {@link http://thequod.de/contact}.
 *
 * @package evocore
 */
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );


/**
 * Template tag. Output content-type header
 *
 * @param string content-type; override for RSS feeds
 */
function header_content_type( $type = 'text/html', $charset = '#' )
{
	global $io_charset;
	global $content_type_header;

	$content_type_header = 'Content-type: '.$type;

	if( !empty($charset) )
	{
		if( $charset == '#' )
		{
			$charset = $io_charset;
		}

		$content_type_header .= '; charset='.$charset;
	}

	header( $content_type_header );
}


/**
 * This is a placeholder for future development.
 *
 * @param string content-type; override for RSS feeds
 * @param integer seconds
 * @param string charset
 * @param boolean flush already collected content from the PageCache
 */
function headers_content_mightcache( $type = 'text/html', $max_age = '#', $charset = '#', $flush_pagecache = true )
{
	global $Messages, $is_admin_page;
	global $PageCache, $Debuglog;

	header_content_type( $type, $charset );

	if( empty($max_age) || $is_admin_page || is_logged_in() || $Messages->count() )
	{	// Don't cache if no max_age given
		// + NEVER EVER allow admin pages to cache
		// + NEVER EVER allow logged in data to be cached
		// + NEVER EVER allow transactional Messages to be cached!:
		header_nocache();

		// Check server caching too, but note that this is a different caching process then caching on the client
		// It's important that this is a double security check only and server caching should be prevented before this
		// If something should not be cached on the client, it should never be cached on the server either
		if( !empty( $PageCache ) )
		{ // Abort PageCache collect
			$Debuglog->add( 'Abort server caching in headers_content_mightcache() function. This should have been prevented!' );
			$PageCache->abort_collect( $flush_pagecache );
		}
		return;
	}

	// If we are on a "normal" page, we may, under some circumstances, tell the browser it can cache the data.
	// This MAY be extremely confusing though, every time a user logs in and gets back to a screen with no evobar!
	// This cannot be enabled by default and requires admin switches.

	// For feeds, it is a little bit less confusing. We might want to have the param enabled by default in that case.

	// WARNING: extra special care needs to be taken before ever caching a blog page that might contain a form or a comment preview
	// having user details cached would be extremely bad.

	// in the meantime...
	header_nocache();
}


/**
 * Get URL to return
 *
 * @return string URL
 */
function get_returnto_url()
{
	global $Hit, $Blog, $baseurl, $ReqHost;

	// See if there's a redirect_to request param given:
	$redirect_to = param( 'redirect_to', 'url', '' );

	if( empty( $redirect_to ) )
	{	// If default redirect_to param is not defined:
		if( ! empty( $Hit->referer ) )
		{	// Use referer page:
			$redirect_to = $Hit->referer;
		}
		elseif( isset( $Blog ) && is_object( $Blog ) )
		{	// Use collection default page URL:
			$redirect_to = $Blog->get( 'url' );
		}
		else
		{	// Use base URL:
			$redirect_to = $baseurl;
		}
	}
	elseif( $redirect_to[0] == '/' )
	{	// relative URL, prepend current host:
		$redirect_to = $ReqHost.$redirect_to;
	}

	return $redirect_to;
}


/**
 * Check if the requested URL is internal system URL
 * (base URL or URL of one collection from this system)
 *
 * @param string URL
 * @return boolean
 */
function is_internal_url( $url )
{
	global $Blog, $basehost, $baseurl;

	if( strpos( $url, $baseurl ) === 0 ||
	    strpos( $url, force_https_url( $baseurl ) ) === 0 ||
	    ( ! empty( $Blog ) && strpos( $url, $Blog->gen_baseurl() ) === 0 ) ||
	    ( ! empty( $Blog ) && strpos( $url, force_https_url( $Blog->gen_baseurl() ) ) === 0 ) )
	{	// The URL is base URL or URL of current collection:
		return true;
	}

	$url_domain = preg_replace( '~(https?://|//)([^/]+)/?.*~i', '$2', $url );

	if( preg_match( '~\.'.preg_quote( $basehost, '~' ).'(:\d+)?$~', $url_domain ) )
	{	// The URL goes to a subdomain of basehost:
		return true;
	}

	// Check if URL domain is used as absolute URL for at least 1 collection on the system:
	global $DB;

	$abs_url_coll_SQL = new SQL( 'Find collection with absolute URL by requested URL domain' );
	$abs_url_coll_SQL->SELECT( 'blog_ID' );
	$abs_url_coll_SQL->FROM( 'T_blogs' );
	$abs_url_coll_SQL->WHERE( 'blog_access_type = "absolute"' );
	$abs_url_coll_SQL->WHERE_and( 'blog_siteurl LIKE '.$DB->quote( '%://'.str_replace( '_', '\_', $url_domain.'/%' ) ) );
	$abs_url_coll_SQL->LIMIT( '1' );
	$abs_url_coll_ID = $DB->get_var( $abs_url_coll_SQL );

	// If at least one collection has the same domain as requested URL:
	return ! empty( $abs_url_coll_ID );
}


/**
 * Sends HTTP header to redirect to the previous location (which can be given as function parameter, GET parameter (redirect_to),
 * is taken from {@link Hit::$referer} or {@link $baseurl}).
 *
 * {@link $Debuglog} and {@link $Messages} get stored in {@link $Session}, so they are available after the redirect.
 *
 * @todo fp> do NOT allow $redirect_to = NULL. This leads to spaghetti code and unpredictable behavior.
 *
 * @return boolean false IF blocked AND $return_to_caller_if_forbidden BUT most of the time, this function {@link exit() exits} the php script execution.
 * @param string Destination URL to redirect to
 * @param boolean|integer is this a permanent redirect? if true, send a 301; otherwise a 303 OR response code 301,302,303
 * @param boolean is this a redirected post display? This param may be true only if we should redirect to a post url where the post status is 'redirected'!
 * @param boolean do we want to return to the caller if the redirect is forbidden? (useful when trying to redirect after post edit)
 */
function header_redirect( $redirect_to = NULL, $status = false, $redirected_post = false, $return_to_caller_if_forbidden = false )
{
	/**
	 * put your comment there...
	 *
	 * @var Hit
	 */
	global $Hit;
	global $baseurl, $Collection, $Blog, $htsrv_url, $ReqHost, $ReqURL, $dispatcher;
	global $Session, $Debuglog, $Messages, $debug;
	global $http_response_code, $allow_redirects_to_different_domain;

	if( empty( $redirect_to ) )
	{	// Use automatic return URL if a redirect URL is not defined:
		$redirect_to = get_returnto_url();
	}

	// Keep ONLY allowed params from current URL by config:
	$redirect_to = url_keep_params( $redirect_to );

	$Debuglog->add('Preparing to redirect to: '.$redirect_to, 'request' );

	// Determine if this is an external or internal redirect:

	$external_redirect = true; // Start with worst case, then whitelist:

	if( $redirect_to[0] == '/' || $redirect_to[0] == '?' )
	{ // We stay on the same domain or same page:
		$external_redirect = false;
	}
	elseif( strpos( $redirect_to, $dispatcher ) === 0 )
	{ // $dispatcher is DEPRECATED and pages should use $admin_url URL instead, but at least we're staying on the same site:
		$external_redirect = false;
	}
	elseif( strpos( $redirect_to, $baseurl ) === 0 )
	{
		$Debuglog->add('Redirecting within $baseurl, all is fine.', 'request' );
		$external_redirect = false;
	}
	elseif( strpos( $redirect_to, force_https_url( $baseurl ) ) === 0 )
	{	// Protocol https may be forced for all login, registration and etc. pages:
		$Debuglog->add('Redirecting within https of $baseurl, all is fine.', 'request' );
		$external_redirect = false;
	}
	elseif( ! empty( $Blog ) && strpos( $redirect_to, $Blog->gen_baseurl() ) === 0 )
	{
		$Debuglog->add( 'Redirecting within current collection URL, all is fine.', 'request' );
		$external_redirect = false;
	}
	elseif( ! empty( $Blog ) && strpos( $redirect_to, force_https_url( $Blog->gen_baseurl() ) ) === 0 )
	{	// Protocol https may be forced for all login, registration and etc. pages:
		$Debuglog->add('Redirecting within https of current collection URL, all is fine.', 'request' );
		$external_redirect = false;
	}


	// Remove login and pwd parameters from URL, so that they do not trigger the login screen again (and also as global security measure):
	$redirect_to = preg_replace( '~(?<=\?|&) (login|pwd) = [^&]+ ~x', '', $redirect_to );

	if( $external_redirect == false )
	{	// (blueyed>) Remove "confirm(ed)?" from redirect_to so it doesn't do the same thing twice
		// TODO: fp> confirm should be normalized to confirmed
		$redirect_to = preg_replace( '~(?<=\?|&) (confirm(ed)?) = [^&]+ ~x', '', $redirect_to );
	}


	$allow_collection_redirect = false;

	if( $external_redirect
		&& $allow_redirects_to_different_domain == 'all_collections_and_redirected_posts'
		&& ! $redirected_post )
	{	// If a redirect is external and we allow to redirect to all collection domains:
		$allow_collection_redirect = is_internal_url( $redirect_to );
	}

	// Check if we're trying to redirect to an external URL:
	if( $external_redirect // Attempting external redirect
		&& ( $allow_redirects_to_different_domain != 'always' ) // Always allow redirects to different domains is not set
		&& ( ! $allow_collection_redirect ) // This is not a redirect to collection domain of this site
		&& ( ! ( in_array( $allow_redirects_to_different_domain, array( 'all_collections_and_redirected_posts', 'only_redirected_posts' ) ) && $redirected_post ) ) ) // This is not a 'redirected' post display request
	{ // Force header redirects into the same domain. Do not allow external URLs.
		$Messages->add( T_('A redirection to an external URL was blocked for security reasons.'), 'error' );
		syslog_insert( 'A redirection to an external URL '.$redirect_to.' was blocked for security reasons.', 'error', NULL );
		if( $return_to_caller_if_forbidden )
		{	// Return to caller meaning we did not redirect:
			return false;
		}
		$redirect_to = $baseurl;
	}

	// Send the predefined cookies:
	evo_sendcookies();

	if( is_integer($status) )
	{
		$http_response_code = $status;
	}
	else
	{
		$http_response_code = $status ? 301 : 303;
	}
	$Debuglog->add('***** REDIRECT TO '.$redirect_to.' (status '.$http_response_code.') *****', 'request' );

	if( ! empty($Session) )
	{	// Session is required here
		if( ! empty( $debug ) )
		{	// Transfer full debug info to next page only when debug is enabled:
			ob_start();
			debug_info( true );
			$current_debug_info = ob_get_clean();
			if( ! empty( $current_debug_info ) )
			{	// Save full debug info into Session, so that it's available after redirect (gets loaded by Session constructor):
				$sess_debug_infos = $Session->get( 'debug_infos' );
				if( empty( $sess_debug_infos ) )
				{
					$sess_debug_infos = array();
				}
				// NOTE: We must encode data in order to avoid error "Session data corrupted" because of special chars on unserialize the data:
				$sess_debug_infos[] = gzencode( $current_debug_info );
				$Session->set( 'debug_infos', $sess_debug_infos, 60 /* expire in 60 seconds */ );
			}
		}

		// Transfer of Messages to next page:
		if( $Messages->count() )
		{	// Set Messages into user's session, so they get restored on the next page (after redirect):
			$Session->set( 'Messages', $Messages );
		 // echo 'Passing Messages to next page';
		}

		$Session->dbsave(); // If we don't save now, we run the risk that the redirect goes faster than the PHP script shutdown.
	}

	// see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
	switch( $http_response_code )
	{
		case 301:
			// This should be a permanent move redirect!
			header_http_response( '301 Moved Permanently' );
			break;

		case 303:
			// This should be a "follow up" redirect
			// Note: Also see http://de3.php.net/manual/en/function.header.php#50588 and the other comments around
			header_http_response( '303 See Other' );
			break;

		case 302:
		default:
			header_http_response( '302 Found' );
	}

	if( $debug &&
	    ! empty( $ReqHost ) &&
	    strpos( $redirect_to, $ReqHost ) !== 0 )
	{	// Append param to redirect from different domain in order to see debug info of the current page after redirect:
		$redirect_to = url_add_param( $redirect_to, 'get_redirected_debuginfo_from_sess_ID='.$Session->ID, '&' );
	}

	// debug_die($redirect_to);
	if( headers_sent($filename, $line) )
	{
		debug_die( sprintf('Headers have already been sent in %s on line %d.', basename($filename), $line)
						.'<br />Cannot <a href="'.htmlspecialchars($redirect_to).'">redirect</a>.' );
	}
	header( 'Location: '.$redirect_to, true, $http_response_code ); // explictly setting the status is required for (fast)cgi
	exit(0);
}


/**
 * Redirect to URL with additional checking from email log
 *
 * @param string Redirect URL
 * @param integer Header response status code: 301, 302, 303
 * @param string Email log content, NULL - if we need to get email log message from DB by email log ID and key
 * @param string Email log ID
 * @param string Email log key
 */
function header_redirect_from_email( $redirect_to, $status = false, $email_log_message = NULL, $email_log_ID = NULL, $email_log_key = NULL )
{
	global $baseurl;

	if( empty( $redirect_to ) )
	{	// Use base site URL for redirect if it is not provided:
		$redirect_to = $baseurl;
	}

	// 1) Try to redirect if it is allowed by config $allow_redirects_to_different_domain,
	// (Use $return_to_caller_if_forbidden = true in order to return false without redirect)
	$redirect_result = header_redirect( $redirect_to, $status, false, true );
	// May be EXITed here!

	// 2) Otherwise(when $redirect_result === false) use additional checking by email log:
	if( ! check_redirect_url_by_email_log( $redirect_to, $email_log_message, $email_log_ID, $email_log_key ) )
	{	// Deny redirect to URL what is not found in the email message:
		$redirect_to = $baseurl;
	}

	// Campaign author explicitly wanted to link to an external URL:
	// Use php function header() instead of b2evolution core function header_redirect(),
	// because we already used it above to redirect and it can prevent redirection depending
	// on some advanced settings like $allow_redirects_to_different_domain!
	header( 'Location: '.$redirect_to, true, $status ); // explictly setting the status is required for (fast)cgi
	exit(0);
}


/**
 * Sends HTTP headers to avoid caching of the page at the browser level
 * (at least without revalidating with the server to make sure whether the content has changed or not).
 *
 * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
 */
function header_nocache( $timestamp = NULL )
{
	global $servertimenow;
	if( empty($timestamp) )
	{
		$timestamp = $servertimenow;
	}

	header('Expires: '.gmdate('r',$timestamp));
	header('Last-Modified: '.gmdate('r',$timestamp));
	header('Cache-Control: no-cache, must-revalidate');
	header('Pragma: no-cache');
}


/**
 * This is to "force" (strongly suggest) caching.
 *
 * WARNING: use this only for STATIC content that does NOT depend on the current user.
 *
 * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
 */
function header_noexpire()
{
	global $servertimenow;
	header('Expires: '.gmdate('r', $servertimenow + 31536000)); // 86400*365 (1 year)
}


/**
 * Generate an etag to identify the version of the current page.
 * We use this primarily to make a difference between the same page that has been generated for anonymous users
 * and a version that has been generated for a specific user.
 *
 * A common problem without this would be that when users log out, the page cache would tell them "304 Not Modified"
 * based on the date of the cache and then the browser would show a locally cached version of the page that includes
 * the evobar.
 *
 * When a specific user logs out, the browser will send back the Etag of the logged in version it got and we will
 * be able to detect that this is not a "304 Not Modified" case -> we will send back the anonymou version of the page.
 */
function gen_current_page_etag()
{
	global $current_User, $Messages;

	if( isset($current_User) )
	{
		$etag = 'user:'.$current_User->ID;
	}
	else
	{
		$etag = 'user:anon';
	}

	if( $Messages->count() )
	{	// This case has never been observed yet, but let's forward protect us against client side cached messages
		$etag .= '-msg:'.md5($Messages->get_string('',''));
	}

	return '"'.$etag.'"';
}


/**
 * This adds teh etag header
 *
 * @param string etag MUST be "quoted"
 */
function header_etag( $etag )
{
	header( 'ETag: '.$etag );
}


/**
 * Get global title matching filter params
 *
 * Outputs the title of the category when you load the page with <code>?cat=</code>
 * Display "Archive Directory" title if it has been requested
 * Display "Latest comments" title if these have been requested
 * Display "Statistics" title if these have been requested
 * Display "User profile" title if it has been requested
 *
 * @todo single month: Respect locales datefmt
 * @todo single post: posts do no get proper checking (wether they are in the requested blog or wether their permissions match user rights,
 * thus the title sometimes gets displayed even when it should not. We need to pre-query the ItemList instead!!
 * @todo make it complete with all possible params!
 *
 * @param array params
 *        - "auto_pilot": "seo_title": Use the SEO title autopilot. (Default: "none")
 */
function get_request_title( $params = array() )
{
	global $MainList, $preview, $disp, $action, $Collection, $Blog, $admin_url;

	$r = array();

	$params = array_merge( array(
			'auto_pilot'          => 'none', // This can be used to override several params at once. Possible value: 'seo_title'
			'title_before'        => '',
			'title_after'         => '',
			'title_none'          => '',
			'title_single_disp'   => true,
			'title_single_before' => '#',
			'title_single_after'  => '#',
			'title_page_disp'     => true,
			'title_page_before'   => '#',
			'title_page_after'    => '#',
			'title_widget_page_disp'   => false,	// We never want a title to be automatically displayed on a widget page.
			'title_widget_page_before' => '#',
			'title_widget_page_after'  => '#',
			'title_terms_disp'    => true,
			'title_terms_before'  => '#',
			'title_terms_after'   => '#',
			'glue'                => ' - ',
			'format'              => 'htmlbody',
			// Title for each disp:
			// fp> TODO: Make a global array of $disp => Clear text disp
			'anonpost_text'       => '#',// # - 'New [Post]' ('Post' is item type name)
			'arcdir_text'         => T_('Archive Directory'),
			'catdir_text'         => T_('Category Directory'),
			'mediaidx_text'       => T_('Photo Index'),
			'postidx_text'        => T_('Post Index'),
			'search_text'         => T_('Search'),
			'sitemap_text'        => T_('Site Map'),
			'msgform_text'        => T_('Contact'),
			'messages_text'       => T_('Messages'),
			'contacts_text'       => T_('Contacts'),
			'requires_login_text_seo' => T_('Login Required'),
			'login_text'          => /* TRANS: trailing space = verb */ T_('Login '),
			'register_text'       => T_('Register'),
			'register_finish_text'=> T_('Finish Registration'),
			'req_activate_email'  => T_('Account activation'),
			'account_activation'  => T_('Account activation'),
			'lostpassword_text'   => T_('Lost your password?'),
			'profile_text'        => T_('User Profile'),
			'avatar_text'         => T_('Profile picture'),
			'pwdchange_text'      => T_('Password change'),
			'userprefs_text'      => T_('User preferences'),
			'user_text'           => T_('User: %s'),
			'users_text'          => T_('Users'),
			'closeaccount_text'   => T_('Account closure'),
			'subs_text'           => T_('Notifications & Subscriptions'),
			'visits_text'         => T_('Who visited my profile?'),
			'comments_text'       => T_('Latest Comments'),
			'feedback-popup_text' => T_('Feedback'),
			'edit_comment_text'   => T_('Editing comment'),
			'front_text'          => '',		// We don't want to display a special title on the front page
			'posts_text'          => '#',		// Automatic - display filters
			'useritems_text'      => T_('User posts'),
			'usercomments_text'   => T_('User comments'),
			'download_head_text'  => T_('Download').' - $file_title$ - $post_title$',
			'download_body_text'  => '',
			// fp> TODO: verify if we really want this:
			'display_edit_links'  => true, // Display the links to advanced editing on disp=edit|edit_comment
			'edit_links_template' => array(), // More params for the links to advanced editing on disp=edit|edit_comment
			'tags_text'           => T_('Tags'),
			'flagged_text'        => T_('Flagged posts'),
			'mustread_text'       => T_('Must Read Items'),
			'help_text'           => T_('In case of issues with this site...'),
			'compare_text'           => /* TRANS: title for disp=compare */ T_('%s compared'),
			'compare_text_separator' => /* TRANS: title separator for disp=compare */ ' '.T_('vs').' ',
			'proposechange_text'     => T_('Propose change for Item %s'),
		), $params );

	if( $params['auto_pilot'] == 'seo_title' )
	{	// We want to use the SEO title autopilot. Do overrides:
		$params['format'] = 'htmlhead';
		$params['title_after'] = $params['glue'].$Blog->get('name');
		$params['title_single_after'] = '';
		$params['title_page_after'] = '';
		$params['title_none'] = $Blog->dget('name','htmlhead');
	}


	$before = $params['title_before'];
	$after = $params['title_after'];

	switch( $disp )
	{
		case 'front':
			// We are requesting a front page:
			if( !empty( $params['front_text'] ) )
			{
				$r[] = $params['front_text'];
			}
			break;

		case 'arcdir':
			// We are requesting the archive directory:
			$r[] = $params['arcdir_text'];
			break;

		case 'catdir':
			// We are requesting the archive directory:
			$r[] = $params['catdir_text'];
			break;

		case 'mediaidx':
			$r[] = $params['mediaidx_text'];
			break;

		case 'postidx':
			$r[] = $params['postidx_text'];
			break;

		case 'sitemap':
			$r[] = $params['sitemap_text'];
			break;

		case 'search':
			$r[] = $params['search_text'];
			break;

		case 'comments':
			// We are requesting the latest comments:
			global $Item;
			if( isset( $Item ) )
			{
				$r[] = sprintf( $params['comments_text'] . T_(' on %s'), $Item->get('title') );
			}
			else
			{
				$r[] = $params['comments_text'];
			}
			break;

		case 'feedback-popup':
			// We are requesting the comments on a specific post:
			// Should be in first position
			$Item = & $MainList->get_by_idx( 0 );
			$r[] = sprintf( $params['feedback-popup_text'] . T_(' on %s'), $Item->get('title') );
			break;

		case 'profile':
			// We are requesting the user profile:
			$r[] = $params['profile_text'];
			break;

		case 'avatar':
			// We are requesting the user avatar:
			$r[] = $params['avatar_text'];
			break;

		case 'pwdchange':
			// We are requesting the user change password:
			$r[] = $params['pwdchange_text'];
			break;

		case 'userprefs':
			// We are requesting the user preferences:
			$r[] = $params['userprefs_text'];
			break;

		case 'subs':
			// We are requesting the subscriptions screen:
			$r[] = $params['subs_text'];
			break;

		case 'register_finish':
			// We are requesting the register finish form:
			$r[] = $params['register_finish_text'];
			break;

		case 'visits':
			// We are requesting the profile visits screen:
			$user_ID = param( 'user_ID', 'integer', 0 );
			$r[] = $params['visits_text'];
			break;

		case 'msgform':
			// We are requesting the message form:
			$msgform_title = utf8_trim( $Blog->get_setting( 'msgform_title' ) );
			$r[] = empty( $msgform_title ) ? $params['msgform_text'] : $msgform_title;
			break;

		case 'threads':
		case 'messages':
			// We are requesting the messages form
			global $disp_detail;
			$thrd_ID = param( 'thrd_ID', 'integer', 0 );
			if( ! empty( $thrd_ID ) )
			{	// We get a thread title by ID
				load_class( 'messaging/model/_thread.class.php', 'Thread' );
				$ThreadCache = & get_ThreadCache();
				if( $Thread = $ThreadCache->get_by_ID( $thrd_ID, false ) )
				{	// Thread exists and we get a title
					if( $params['auto_pilot'] == 'seo_title' )
					{	// Display thread title only for tag <title>
						$r[] = $Thread->title;
						break;
					}
				}
			}

			if( $disp_detail == 'msgform' )
			{	// disp=msgform for logged in user:
				$msgform_title = utf8_trim( $Blog->get_setting( 'msgform_title' ) );
				$r[] = empty( $msgform_title ) ? $params['msgform_text'] : $msgform_title;
			}
			else
			{
				$r[] = strip_tags( $params['messages_text'] );
			}
			break;

		case 'contacts':
			// We are requesting the message form:
			$r[] = $params['contacts_text'];
			break;

		case 'access_requires_login':
		case 'content_requires_login':
			// We are requesting the login form when anonymous user has no access to the Collection:
			if( $params['auto_pilot'] == 'seo_title' )
			{	// Use text only for <title> tag in <head>:
				$r[] = $params['requires_login_text_seo'];
			}
			break;

		case 'login':
			// We are requesting the login form:
			if( $action == 'req_activate_email' )
			{
				$r[] = $params['req_activate_email'];
			}
			else
			{
				$r[] = $params['login_text'];
			}
			break;

		case 'register':
			// We are requesting the registration form:
			$r[] = $params['register_text'];
			break;

		case 'activateinfo':
			// We are requesting the activate info form:
			$r[] = $params['account_activation'];
			break;

		case 'lostpassword':
			// We are requesting the lost password form:
			$r[] = $params['lostpassword_text'];
			break;

		case 'single':
		case 'page':
		case 'widget_page':
		case 'terms':
			// We are displaying a single message:
			if( $preview )
			{	// We are requesting a post preview:
				$r[] = /* TRANS: Noun */ T_('PREVIEW');
			}
			elseif( $params['title_'.$disp.'_disp'] && isset( $MainList ) )
			{
				$r = array_merge( $r, $MainList->get_filter_titles( array( 'visibility', 'hide_future' ), $params ) );
			}
			if( $params['title_'.$disp.'_before'] != '#' )
			{
				$before = $params['title_'.$disp.'_before'];
			}
			if( $params['title_'.$disp.'_after'] != '#' )
			{
				$after = $params['title_'.$disp.'_after'];
			}
			break;

		case 'download':
			// We are displaying a download page:
			global $download_Link;

			$download_text = ( $params['format'] == 'htmlhead' ) ? $params['download_head_text'] : $params['download_body_text'];
			if( strpos( $download_text, '$file_title$' ) !== false )
			{ // Replace a mask $file_title$ with real file name
				$download_File = & $download_Link->get_File();
				$download_text = str_replace( '$file_title$', $download_File->get_name(), $download_text );
			}
			if( strpos( $download_text, '$post_title$' ) !== false )
			{ // Replace a mask $file_title$ with real file name
				$download_text = str_replace( '$post_title$', implode( $params['glue'], $MainList->get_filter_titles( array( 'visibility', 'hide_future' ) ) ), $download_text );
			}
			$r[] = $download_text;
			break;

		case 'user':
			// We are requesting the user page:
			$user_ID = param( 'user_ID', 'integer', 0 );
			$UserCache = & get_UserCache();
			$User = & $UserCache->get_by_ID( $user_ID, false, false );
			$user_login = $User ? $User->get( 'login' ) : '';
			$r[] = sprintf( $params['user_text'], $user_login );
			break;

		case 'users':
			$r[] = $params['users_text'];
			break;

		case 'closeaccount':
			$r[] = $params['closeaccount_text'];
			break;

		case 'anonpost':
			if( $params['anonpost_text'] == '#' )
			{	// Initialize default auto title:
				$new_Item = get_session_Item( 0, true );
				$r[] = sprintf( T_('New [%s]'), $new_Item->get_type_setting( 'name' ) );
			}
			else
			{	// Use custom title from param:
				$r[] = $params['anonpost_text'];
			}
			break;

		case 'edit':
			global $edited_Item;
			$type_name = $edited_Item->get_type_setting( 'name' );

			$action = param_action(); // Edit post by switching into 'In skin' mode from Back-office
			$p = param( 'p', 'integer', 0 ); // Edit post from Front-office
			$post_ID = param ( 'post_ID', 'integer', 0 ); // Update the edited post( If user is redirected to edit form again with some error messages )
			$cp = param( 'cp', 'integer', 0 ); // Copy post from Front-office
			if( $action == 'edit_switchtab' || $p > 0 || $post_ID > 0 )
			{	// Edit post
				$title = sprintf( T_('Edit [%s]'), $type_name );
			}
			else if( $cp > 0 )
			{	// Copy post
				$title = sprintf( T_('Duplicate [%s]'), $type_name );
			}
			else
			{	// Create post
				$title = sprintf( T_('New [%s]'), $type_name );
			}
			if( $params['display_edit_links'] && $params['auto_pilot'] != 'seo_title' )
			{ // Add advanced edit and close icon
				$params['edit_links_template'] = array_merge( array(
						'before'              => '<span class="title_action_icons">',
						'after'               => '</span>',
						'advanced_link_class' => '',
						'close_link_class'    => '',
					), $params['edit_links_template'] );

				global $edited_Item;
				if( !empty( $edited_Item ) && $edited_Item->ID > 0 )
				{ // Set the cancel editing url as permanent url of the item
					$cancel_url = $edited_Item->get_permanent_url();
				}
				else
				{ // Set the cancel editing url to home page of the blog
					$cancel_url = $Blog->gen_blogurl();
				}

				$title .= $params['edit_links_template']['before'];
				if( check_user_perm( 'admin', 'restricted' ) )
				{
					global $advanced_edit_link;
					$title .= action_icon( T_('Go to advanced edit screen'), 'edit', $advanced_edit_link['href'], ' '.T_('Advanced editing'), NULL, 3, array(
							'onclick' => $advanced_edit_link['onclick'],
							'class'   => $params['edit_links_template']['advanced_link_class'].' action_icon',
							'data-shortcut' => 'f2,ctrl+f2',
						) );
				}
				$title .= action_icon( T_('Cancel editing'), 'close', $cancel_url, ' '.T_('Cancel editing'), NULL, 3, array(
						'class' => $params['edit_links_template']['close_link_class'].' action_icon',
					) );
				$title .= $params['edit_links_template']['after'];
			}
			$r[] = $title;
			break;

		case 'proposechange':
			global $edited_Item;
			$r[] = sprintf( $params['proposechange_text'], '"'.$edited_Item->get_title().'"' );
			break;

		case 'edit_comment':
			global $comment_Item, $edited_Comment;
			$title = $params['edit_comment_text'];
			if( $params['display_edit_links'] && $params['auto_pilot'] != 'seo_title' )
			{ // Add advanced edit and close icon
				$params['edit_links_template'] = array_merge( array(
						'before'              => '<span class="title_action_icons">',
						'after'               => '</span>',
						'advanced_link_class' => '',
						'close_link_class'    => '',
					), $params['edit_links_template'] );

				$title .= $params['edit_links_template']['before'];
				if( check_user_perm( 'admin', 'restricted' ) )
				{
					$advanced_edit_url = url_add_param( $admin_url, 'ctrl=comments&amp;action=edit&amp;blog='.$Blog->ID.'&amp;comment_ID='.$edited_Comment->ID );
					$title .= action_icon( T_('Go to advanced edit screen'), 'edit', $advanced_edit_url, ' '.T_('Advanced editing'), NULL, 3, array(
							'onclick' => 'return switch_edit_view();',
							'class'   => $params['edit_links_template']['advanced_link_class'].' action_icon',
						) );
				}
				if( empty( $comment_Item ) )
				{
					$comment_Item = & $edited_Comment->get_Item();
				}
				if( !empty( $comment_Item ) )
				{
					$title .= action_icon( T_('Cancel editing'), 'close', url_add_tail( $comment_Item->get_permanent_url(), '#c'.$edited_Comment->ID ), ' '.T_('Cancel editing'), NULL, 3, array(
							'class' => $params['edit_links_template']['close_link_class'].' action_icon',
						) );
				}
				$title .= $params['edit_links_template']['after'];
			}
			$r[] = $title;
			break;

		case 'useritems':
			// We are requesting the user items list:
			$r[] = $params['useritems_text'];
			break;

		case 'usercomments':
			// We are requesting the user comments list:
			$r[] = $params['usercomments_text'];
			break;

		case 'tags':
			// We are requesting the tags directory:
			$r[] = $params['tags_text'];
			break;

		case 'flagged':
			// We are requesting the flagged posts list:
			$r[] = $params['flagged_text'];
			break;

		case 'mustread':
			// We are requesting the must read posts list:
			$r[] = $params['mustread_text'];
			break;

		case 'help':
			$r[] = $params['help_text'];
			break;

		case 'compare':
			// We are requesting the compare list:
			$items = trim( param( 'items', '/^[\d,]*$/' ), ',' );

			if( ! empty( $items ) )
			{	// It at least one item is selected to compare
				$items = explode( ',', $items );

				// Load all requested posts into the cache:
				$ItemCache = & get_ItemCache();
				$ItemCache->load_list( $items );

				$compare_item_titles = array();
				foreach( $items as $item_ID )
				{
					if( $Item = & $ItemCache->get_by_ID( $item_ID, false, false ) )
					{	// Use only existing Item:
						$compare_item_titles[] = $Item->get( 'title' );
					}
				}

				$r[] = sprintf( $params['compare_text'], implode( $params['compare_text_separator'], $compare_item_titles ) );
			}

			break;

		case 'posts':
			// We are requesting a posts page:
			if( $params['posts_text'] != '#' )
			{
				$r[] = $params['posts_text'];
				break;
			}
			// No break if empty, Use title from default case
		default:
			if( isset( $MainList ) )
			{
				$r = array_merge( $r, $MainList->get_filter_titles( array( 'visibility', 'hide_future', 'itemtype' ), $params ) );
			}
			break;
	}


	if( ! empty( $r ) )
	{	// We have at leats one title match:
		$r = implode( $params['glue'], $r );
		if( ! empty( $r ) )
		{	// This is in case we asked for an empty title (e-g for search)
			$r = $before.format_to_output( $r, $params['format'] ).$after;
		}
	}
	elseif( !empty( $params['title_none'] ) )
	{
		$r = $params['title_none'];
	}
	else
	{	// never return array()
		$r = '';
	}

	return $r;
}


/**
 * Display a global title matching filter params
 *
 * @param array params
 *        - "auto_pilot": "seo_title": Use the SEO title autopilot. (Default: "none")
 */
function request_title( $params = array() )
{
	$r = get_request_title( $params );

	if( !empty( $r ) )
	{ // We have something to display:
		echo $r;
	}
}


/**
 * Returns a "<base />" tag and remembers that we've used it ({@link regenerate_url()} needs this).
 *
 * @param string URL to use (this gets used as base URL for all relative links on the HTML page)
 * @return string
 */
function base_tag( $url, $target = NULL )
{
	global $base_tag_set;
	$base_tag_set = $url;
	echo '<base href="'.$url.'"';

	if( !empty($target) )
	{
		echo ' target="'.$target.'"';
	}
	echo " />\n";
}


/**
 * Robots tag
 *
 * Outputs the robots meta tag if necessary
 */
function robots_tag()
{
	global $robots_index, $robots_follow;

	if( is_null($robots_index) && is_null($robots_follow) )
	{
		return;
	}

	$r = '<meta name="robots" content="';

	if( $robots_index === false )
		$r .= 'NOINDEX';
	else
		$r .= 'INDEX';

	$r .= ',';

	if( $robots_follow === false )
		$r .= 'NOFOLLOW';
	else
		$r .= 'FOLLOW';

	$r .= '" />'."\n";

	echo $r;
}


/**
 * Output a link to current blog.
 *
 * We need this function because if no Blog is currently active (some admin pages or site pages)
 * then we'll go to the general home.
 */
function blog_home_link( $before = '', $after = '', $blog_text = 'Blog', $home_text = 'Home' )
{
	global $Collection, $Blog, $baseurl;

	if( !empty( $Blog ) )
	{
		echo $before.'<a href="'.$Blog->get( 'url' ).'">'.$blog_text.'</a>'.$after;
	}
	elseif( !empty($home_text) )
	{
		echo $before.'<a href="'.$baseurl.'">'.$home_text.'</a>'.$after;
	}
}


/**
 * Expose PHP variable to JS variable in order to print out them into <script> before </body>
 *
 * @param string Name
 * @param string Value
 */
function expose_var_to_js( $name, $value, $parent_object = NULL )
{
	global $evo_exposed_js_vars;

	if( ! is_array( $evo_exposed_js_vars ) )
	{	// Initialize array once:
		$evo_exposed_js_vars = array();
	}

	if( ! empty( $parent_object ) )
	{
		if( ! isset( $evo_exposed_js_vars[$parent_object] ) )
		{
			$evo_exposed_js_vars[$parent_object] = array();
		}

		$evo_exposed_js_vars[$parent_object][$name] = $value;
	}
	else
	{
		$evo_exposed_js_vars[ $name ] = $value;
	}
}


/**
 * Include ALL exposed JS variables into <script>
 */
function include_js_vars()
{
	global $evo_exposed_js_vars;

	if( empty( $evo_exposed_js_vars ) ||
	    ! is_array( $evo_exposed_js_vars ) )
	{	// No exposed JS variables found:
		return;
	}

	echo "<script>\n/* <![CDATA[ */\n";

	foreach( $evo_exposed_js_vars as $var_name => $var_value )
	{
		if( is_array( $var_value ) )
		{
			$var_value = evo_json_encode( $var_value, JSON_FORCE_OBJECT );
		}
		elseif( is_bool( $var_value ) )
		{
			$var_value = $var_value ? 'true' : 'false';
		}
		echo 'var '.$var_name.' = '.$var_value.";\n";
	}

	echo "\n/* ]]> */\n</script>";
}


/**
 * Get library url of JS or CSS file by file name or alias
 *
 * @param string File or Alias name
 * @param boolean|string 'relative' or true (relative to <base>),
 *                       'absolute'(for absolute url)
 *                       'rsc_url' (relative to $rsc_url),
 *                       'blog' (relative to current blog URL -- may be subdomain or custom domain)
 * @param string 'js' or 'css' or 'build'
 * @return string URL
 * @param string version number to append at the end of requested url to avoid getting an old version from the cache
 */
function get_require_url( $lib_file, $relative_to = 'rsc_url', $subfolder = 'js', $version = '#' )
{
	global $library_local_urls, $library_cdn_urls, $use_cdns, $debug, $rsc_url, $rsc_uri;
	global $Collection, $Blog, $baseurl, $assets_baseurl, $ReqURL;

	if( $relative_to == 'blog' && ( is_admin_page() || empty( $Blog ) ) )
	{	// Make sure we never use resource url relative to any blog url in case of an admin page ( important in case of multi-domain installations ):
		$relative_to = 'rsc_url';
	}

	// Check if we have a public CDN we want to use for this library file:
	if( $use_cdns && ! empty( $library_cdn_urls[ $lib_file ] ) )
	{ // Rewrite local urls with public CDN urls if they are defined in _advanced.php
		$library_local_urls[ $lib_file ] = $library_cdn_urls[ $lib_file ];
		// Don't append version for global CDN urls
		$version = NULL;
	}

	if( ! empty( $library_local_urls[ $lib_file ] ) )
	{ // We are requesting an alias
		if( $debug && ! empty( $library_local_urls[ $lib_file ][1] ) )
		{ // Load JS file for debug mode (optional)
			$lib_file = $library_local_urls[ $lib_file ][1];
		}
		else
		{ // Load JS file for production mode
			$lib_file = $library_local_urls[ $lib_file ][0];
		}

		if( $relative_to === 'relative' || $relative_to === true )
		{ // Aliases cannot be relative to <base>, make it relative to $rsc_url
			$relative_to = 'rsc_url';
		}
	}

	if( strpos( $lib_file, 'ext:' ) === 0 || strpos( $lib_file, 'customized:' ) === 0 )
	{	// This file must be loaded from subfolder '/rsc/ext/' or '/rsc/customized/' :
		$subfolder = strpos( $lib_file, 'ext:' ) === 0 ? 'ext' : 'customized';
		// Remove prefix 'ext:' from beginning of the file:
		$lib_file = substr( $lib_file, strlen( $subfolder ) + 1 );
	}

	if( $relative_to === 'relative' || $relative_to === true )
	{ // Make the file relative to current page <base>:
		$lib_url = $lib_file;
	}
	elseif( $relative_to === 'absolute' || preg_match( '~^(https?:)?//~', $lib_file ) )
	{	// It's already an absolute url, keep it as is:
		// (used to require CSS and JS files from Skin and Plugin because there we always use absolute URLs)
		$lib_url = $lib_file;
	}
	elseif( $relative_to === 'blog' && ! empty( $Blog ) )
	{ // Get the file from $rsc_uri relative to the current blog's domain (may be a subdomain or a custom domain):
		if( $assets_baseurl !== $baseurl )
		{ // We are using a specific domain, don't try to load from blog specific domain
			$lib_url = $rsc_url.$subfolder.'/'.$lib_file;
		}
		else
		{
			$lib_url = $Blog->get_local_rsc_url().$subfolder.'/'.$lib_file;
		}
	}
	elseif( $relative_to === 'siteskin' )
	{	// Get the file from current site skin if it is enabled otherwise from relative current page or head tag <base>:
		if( $site_Skin = & get_site_Skin() )
		{
			$lib_url = $site_Skin->get_url().$lib_file;
		}
		else
		{
			$lib_url = $lib_file;
		}
	}
	elseif( $relative_to === 'rsc_uri' )
	{ // Get the file from $rsc_uri:
		$lib_url = $rsc_uri.$subfolder.'/'.$lib_file;
	}
	else
	{ // Get the file from $rsc_url:
		$lib_url = $rsc_url.$subfolder.'/'.$lib_file;
	}

	if( ! empty( $version ) )
	{ // Be sure to get a fresh copy of this CSS file after application upgrades:
		if( $version == '#' )
		{
			global $app_version_long, $Skin;

			$version = $app_version_long;

			if( ( $relative_to == 'relative' || $relative_to === true ) && ! is_admin_page() && isset( $Skin ) )
			{	// Prepand skin version to clear file from browser cache after skin switching:
				$version = $Skin->folder.'+'.$Skin->version.'+'.$version;
			}
		}
		$lib_url = url_add_param( $lib_url, 'v='.$version );
	}

	if( preg_match( '~^https://~', $ReqURL ) )
	{ // If base url is safe then fix all media urls to protocol-relative format:
		$lib_url = preg_replace( '~^http://~', '//', $lib_url );
	}

	return $lib_url;
}


/**
 * Check if the requested file is bundled in another
 *
 * @param string alias, url or filename (relative to rsc/js) for javascript file
 * @param boolean|string Is the file's path relative to the base path/url?
 * @param string 'js' or 'css' or 'build'
 * @param string version number to append at the end of requested url to avoid getting an old version from the cache
 * @return integer Index of first file that was dequeued because it is bundled inside current requested file
 */
function check_bundled_file( $file, $relative_to = 'rsc_url', $subfolder = 'js', $version = '#' )
{
	global $required_js, $required_css, $bundled_files;

	// Store here index of first file that was dequeued because it is bundled inside current requested file:
	$first_dequeued_file_index = NULL;

	if( isset( $bundled_files[ $file ] ) )
	{	// If currently required file contains other JS files which must not be required twice:
		foreach( $bundled_files[ $file ] as $bundled_file )
		{	// Include all bundled files in the global array in order to don't call them twice:
			$bundled_url = strtolower( get_require_url( $bundled_file, $relative_to, $subfolder, $version ) );
			if( $subfolder == 'js' )
			{	// JS file:
				if( empty( $required_js ) || ! in_array( $bundled_url, $required_js ) )
				{	// Include bundled file into this global array in order to don't require this if it will be required further:
					$required_js[] = $bundled_url;
				}
			}
			else // 'css' or 'build'
			{	// CSS file:
				if( empty( $required_css ) || ! in_array( $bundled_url, $required_css ) )
				{	// Include bundled file into this global array in order to don't require this if it will be required further:
					$required_css[] = $bundled_url;
				}
			}
			// Dequeue the file if it was required before:
			$dequeued_file_index = dequeue( $bundled_file, $relative_to );
			if( $first_dequeued_file_index === NULL )
			{	// We need to know first dequeued file in order to insert currently
				// required file in that place instead of insert it as last ordered:
				$first_dequeued_file_index = $dequeued_file_index;
			}
		}
	}

	return $first_dequeued_file_index;
}


/**
 * Memorize that a specific javascript file will be required by the current page.
 * All requested files will be included in the page head only once (when headlines is called)
 *
 * Accepts absolute urls, filenames relative to the rsc/js directory and certain aliases, like 'jquery' and 'jquery_debug'
 * If 'jquery' is used and $debug is set to true, the 'jquery_debug' is automatically swapped in.
 * Any javascript added to the page is also added to the $required_js array, which is then checked to prevent adding the same code twice
 *
 * @param string alias, url or filename (relative to rsc/js) for javascript file
 * @param boolean|string Is the file's path relative to the base path/url?
 * @param boolean 'async' or TRUE to add attribute "async" to load javascript asynchronously,
 *                'defer' to add attribute "defer" asynchronously in the order they occur in the page,
 *                'immediate' or FALSE to load javascript immediately
 * @param boolean TRUE to print script tag on the page, FALSE to store in array to print then inside <head>
 * @param string version number to append at the end of requested url to avoid getting an old version from the cache
 * @param string Position where the CSS files will be inserted, either 'headlines' (inside <head>) or 'footerlines' (before </body>)
 */
function require_js( $js_file, $relative_to = 'rsc_url', $async_defer = false, $output = false, $version = '#', $position = 'headlines' )
{
	global $required_js; // Use this var as global and NOT static, because it is used in other functions(e.g. display_ajax_form(), check_bundled_file())
	global $use_defer;

	if( is_admin_page() && in_array( $js_file, array( 'functions.js', 'ajax.js', 'form_extensions.js', 'extracats.js', 'dynamic_select.js', 'backoffice.js' ) ) )
	{	// Don't require this file on back-office because it is auto loaded by bundled file evo_backoffice.bmin.js:
		return;
	}

	if( is_dequeued( $js_file, $relative_to ) )
	{	// Don't require if the file was already dequeued once:
		return;
	}

	// Get index of first file that was dequeued because it is bundled inside current requested file:
	$first_dequeued_file_index = check_bundled_file( $js_file, $relative_to, 'js', $version );

	if( in_array( $js_file, array( '#jqueryUI#', 'communication.js', 'functions.js' ) ) )
	{	// Dependency : ensure jQuery is loaded
		// Don't use TRUE for $async and $output because it may loads jQuery twice on AJAX request, e.g. on comment AJAX form,
		// and all jQuery UI libraries(like resizable, sortable and etc.) will not work, e.g. on attachments fieldset
		require_js_defer( '#jquery#', $relative_to, false, $version, $position );
	}

	// Get library url of JS file by alias name
	$js_url = get_require_url( $js_file, $relative_to, 'js', $version );

	// Add to headlines, if not done already:
	if( empty( $required_js ) || ! in_array( strtolower( $js_url ), $required_js ) )
	{
		$required_js[] = strtolower( $js_url );

		$script_tag = '<script';
		if( $async_defer == 'async' || $async_defer === true )
		{
			$script_tag .= ' async';
		}
		elseif( use_defer() && $async_defer == 'defer' )
		{
			$script_tag .= ' defer';
		}
		//else 'immediate' or false
		$script_tag .= ' src="'.$js_url.'">';
		$script_tag .= '</script>';

		if( $output )
		{ // Print script tag right here
			echo $script_tag;
		}
		else
		{ // Add script tag to <head>
			if( $position == 'headlines' )
			{
				add_headline( $script_tag, $js_file, $relative_to, $first_dequeued_file_index );
			}
			elseif( $position == 'footerlines' )
			{
				add_footerline( $script_tag, $js_file, $relative_to, $first_dequeued_file_index );
			}
		}
	}

	/* Yura: Don't require this plugin when it is already concatenated in jquery.bundle.js
	 * But we should don't forget it for CDN jQuery file and when js code uses deprecated things of jQuery */
	if( $js_file == '#jquery#' )
	{ // Dependency : The plugin restores deprecated features and behaviors so that older code will still run properly on jQuery 1.9 and later
		require_js_defer( '#jquery_migrate#', $relative_to, $output, $version );
	}
}


/**
 * Require javascript file to load asynchronously with attribute "async"
 *
 * @param string Alias, url or filename (relative to rsc/js) for javascript file
 * @param boolean|string Is the file's path relative to the base path/url?
 * @param boolean TRUE to print script tag on the page, FALSE to store in array to print then inside <head>
 * @param string Version number to append at the end of requested url to avoid getting an old version from the cache
 * @param string Position where the CSS files will be inserted, either 'headlines' (inside <head>) or 'footerlines' (before </body>)
 */
function require_js_async( $js_file, $relative_to = 'rsc_url', $output = false, $version = '#', $position = 'headlines' )
{
	require_js( $js_file, $relative_to, 'async', $output, $version, $position );
}


/**
 * Require javascript file to load asynchronously with attribute "defer" in the order they occur in the page
 *
 * @param string Alias, url or filename (relative to rsc/js) for javascript file
 * @param boolean|string Is the file's path relative to the base path/url?
 * @param boolean TRUE to print script tag on the page, FALSE to store in array to print then inside <head>
 * @param string Version number to append at the end of requested url to avoid getting an old version from the cache
 * @param string Position where the CSS files will be inserted, either 'headlines' (inside <head>) or 'footerlines' (before </body>)
 */
function require_js_defer( $js_file, $relative_to = 'rsc_url', $output = false, $version = '#', $position = 'headlines' )
{
	require_js( $js_file, $relative_to, 'defer', $output, $version, $position );
}


/**
 * Memorize that a specific css that file will be required by the current page.
 * All requested files will be included in the page head only once (when headlines is called)
 *
 * Accepts absolute urls, filenames relative to the rsc/css directory.
 * Set $relative_to_base to TRUE to prevent this function from adding on the rsc_path
 *
 * @param string alias, url or filename (relative to rsc/css) for CSS file
 * @param boolean|string 'relative' or true (relative to <base>) or 'rsc_url' (relative to $rsc_url)  or 'rsc_uri' (relative to $rsc_uri) or 'blog' (relative to current blog URL -- may be subdomain or custom domain)
 * @param string title.  The title for the link tag
 * @param string media.  ie, 'print'
 * @param string version number to append at the end of requested url to avoid getting an old version from the cache
 * @param boolean TRUE to print style tag on the page, FALSE to store in array to print then inside <head> or <body>
 * @param string Position where the CSS files will be inserted, either 'headlines' (inside <head>) or 'footerlines' (before </body>)
 * @param boolean TRUE to load CSS file asynchronously, FALSE otherwise.
 */
function require_css( $css_file, $relative_to = 'rsc_url', $title = NULL, $media = NULL, $version = '#', $output = false, $position = 'headlines', $async = false )
{
	global $required_css; // Use this var as global and NOT static, because it is used in other functions(e.g. check_bundled_file())

	// Which subfolder do we want to use in case of absolute paths? (doesn't appy to 'relative')
	$subfolder = 'css';
	if( $relative_to == 'rsc_url' || $relative_to == 'rsc_uri' || $relative_to == 'blog' )
	{
		if( preg_match( '/\.(bundle|bmin|min)\.css$/', $css_file ) )
		{
			$subfolder = 'build';
		}
	}

	if( is_dequeued( $css_file, $relative_to ) )
	{	// Don't require if the file was already dequeued once:
		return;
	}

	// Get index of first file that was dequeued because it is bundled inside current requested file:
	$first_dequeued_file_index = check_bundled_file( $css_file, $relative_to, $subfolder, $version );

	// Get library url of CSS file by alias name
	$css_url = get_require_url( $css_file, $relative_to, $subfolder, $version );

	// Add to headlines/footerlines, if not done already:
	if( empty( $required_css ) || ! in_array( strtolower( $css_url ), $required_css ) )
	{
		$required_css[] = strtolower( $css_url );

		$stylesheet_tag = '';

		if( $async )
		{
			$stylesheet_tag .= '<link rel="preload" as="style" onload="this.onload=null;this.rel=\'stylesheet\'"';
			$stylesheet_tag .= empty( $title ) ? '' : ' title="'.$title.'"';
			$stylesheet_tag .= empty( $media ) ? '' : ' media="'.$media.'"';
			$stylesheet_tag .= ' href="'.$css_url.'" />';
			$stylesheet_tag .= '<noscript>';
		}
		
		$stylesheet_tag .= '<link type="text/css" rel="stylesheet"';
		$stylesheet_tag .= empty( $title ) ? '' : ' title="'.$title.'"';
		$stylesheet_tag .= empty( $media ) ? '' : ' media="'.$media.'"';
		$stylesheet_tag .= ' href="'.$css_url.'" />';

		if( $async )
		{
			$stylesheet_tag .= '</noscript>';
		}

		if( $output )
		{	// Print stylesheet tag right here
			echo $stylesheet_tag;
		}
		else
		{	// Add stylesheet tag to <head>
			if($position == 'headlines' )
			{
				add_headline( $stylesheet_tag, $css_file, $relative_to, $first_dequeued_file_index );
			}
			elseif( $position == 'footerlines' )
			{
				add_footerline( $stylesheet_tag, $css_file, $relative_to, $first_dequeued_file_index );
			}
		}
	}
}


/**
 * Require CSS file to load asynchronously
 *
 * @param string Alias, url or filename (relative to rsc/css) for CSS file
 * @param boolean|string Is the file's path relative to the base path/url?
 * @param string title.  The title for the link tag
 * @param string media.  ie, 'print'
 * @param string Version number to append at the end of requested url to avoid getting an old version from the cache
 * @param boolean TRUE to print script tag on the page, FALSE to store in array to print then inside <head> or <body>
 * @param string Position where the CSS files will be inserted, either 'headlines' (inside <head>) or 'footerlines' (before </body>)
 */
function require_css_async( $css_file, $relative_to = 'rsc_url', $title = NULL, $media = NULL, $version = '#', $output = false, $position = 'headlines' )
{
	require_css( $css_file, $relative_to, $title, $media, $version, $output, $position, true );
}


/**
 * Dequeue a file from $headlines and $footerlines array by file name or alias
 *
 * @param string alias, url or filename (relative to rsc/js) for javascript file
 * @param boolean|string What group of headlines/footerlines touch to dequeue
 * @return integer|NULL Index/order of the file in global array on required files, NULL - if the file was not found
 */
function dequeue( $file_name, $group_relative_to = '#anygroup#' )
{
	global $headline_include_file, $headline_file_index, $dequeued_headlines;

	if( is_dequeued( $file_name, $group_relative_to ) )
	{	// Don't dequeue twice:
		pre_dump( 'is_dequeued' );
		return NULL;
	}

	// Convert boolean, NULL and etc. values to string format:
	$group_relative_to = strval( $group_relative_to );

	if( ! is_array( $dequeued_headlines ) )
	{	// Initialize array first time:
		$dequeued_headlines = array();
	}

	// Store each dequeued file in order to don't require this next time:
	$dequeued_headlines[ $group_relative_to ][ $file_name ] = true;

	// Find and store here index/order of first file:
	$dequeued_file_index = NULL;

	// Try to find and dequeue headline file:
	if( ! empty( $headline_file_index ) )
	{
		$headline_file_indexes = ( $group_relative_to == '#anygroup#' ? $headline_file_index : array() );
		if( $group_relative_to == '#anygroup#' )
		{	// Dequeue the file from any group:
			$headline_file_indexes = $headline_file_index;
		}
		elseif( isset( $headline_file_index[ $group_relative_to ] ) )
		{	// Dequeue the file only from the requested group:
			$headline_file_indexes = array( $group_relative_to => $headline_file_index[ $group_relative_to ] );
		}
		if( ! empty( $headline_file_indexes ) )
		{	// If relative_to group is found:
			foreach( $headline_file_indexes as $group_key => $group_headlines )
			{
				if( isset( $group_headlines[ $file_name ] ) )
				{	// Dequeue html/include tag with src/href of the file:
					$dequeued_file_index = $headline_file_index[ $group_key ][ $file_name ];
					unset( $headline_include_file[ $group_headlines[ $file_name ] ] );
					// Dequeue index/order of the file:
					unset( $headline_file_index[ $group_key ][ $file_name ] );
					// Don't find the file in next groups because it must be unique:
					return $dequeued_file_index;
				}
			}
		}
	}

	// Footerlines if the files is not found in Headlines above:
	global $footerline_include_file, $footerline_file_index, $dequeued_footerlines;

	if( ! is_array( $dequeued_footerlines ) )
	{	// Initialize array first time:
		$dequeued_footerlines = array();
	}

	// Store each dequeued file in order to don't require this next time:
	$dequeued_footerlines[ $group_relative_to ][ $file_name ] = true;

	// Find and store here index/order of first file:
	$dequeued_file_index = NULL;

	// Try to find and dequeue footerline file:
	if( ! empty( $footerline_file_index ) )
	{	// Try to dequeue footerline file:
		$footerline_file_indexes = ( $group_relative_to == '#anygroup#' ? $footerline_file_index : array() );
		if( $group_relative_to == '#anygroup#' )
		{	// Dequeue the file from any group:
			$footerline_file_indexes = $footerline_file_index;
		}
		elseif( isset( $footerline_file_index[ $group_relative_to ] ) )
		{	// Dequeue the file only from the requested group:
			$footerline_file_indexes = array( $group_relative_to => $footerline_file_index[ $group_relative_to ] );
		}
		if( ! empty( $footerline_file_indexes ) )
		{	// If relative_to group is found:
			foreach( $footerline_file_indexes as $group_key => $group_footerlines )
			{
				if( isset( $group_footerlines[ $file_name ] ) )
				{	// Dequeue html/include tag with src/href of the file:
					$dequeued_file_index = $footerline_file_index[ $group_key ][ $file_name ];
					unset( $footerline_include_file[ $group_footerlines[ $file_name ] ] );
					// Dequeue index/order of the file:
					unset( $footerline_file_index[ $group_key ][ $file_name ] );
					// Don't find the file in next groups because it must be unique:
					return $dequeued_file_index;
				}
			}
		}
	}

	return $dequeued_file_index;
}


/**
 * Check if file was dequeued from required list
 *
 * @param string Alias, url or relative file path of JS/CSS file
 * @param boolean|string Group of file
 */
function is_dequeued( $file_name, $group_relative_to )
{
	global $dequeued_headlines, $dequeued_footerlines;

	// Convert boolean, NULL and etc. values to string format:
	$group_relative_to = strval( $group_relative_to );

	return isset( $dequeued_headlines[ $group_relative_to ][ $file_name ] ) ||
		isset( $dequeued_footerlines[ $group_relative_to ][ $file_name ] );
}


/**
 * Memorize that a specific js helper will be required by the current page.
 * This allows to require JS + SS + do init.
 *
 * All requested helpers will be included in the page head only once (when headlines is called)
 * Requested helpers should add their required translation strings and any other settings
 *
 * @param string helper, name of the required helper
 */
function require_js_helper( $helper = '', $relative_to = 'rsc_url' )
{
	static $helpers;

	if( empty( $helpers ) || !in_array( $helper, $helpers ) )
	{ // Helper not already added, add the helper:

		switch( $helper )
		{
			case 'helper' :
				// main helper object required
				global $debug;
				require_js_defer( '#jquery#', $relative_to ); // dependency
				require_js_defer( 'helper.js', $relative_to );
				add_js_headline('jQuery(document).ready(function()
				{
					b2evoHelper.Init({
						debug:'.( $debug ? 'true' : 'false' ).'
					});
				});');
				break;

			case 'communications' :
				// communications object required
				require_js_helper('helper', $relative_to ); // dependency

				global $dispatcher;
				require_js_defer( 'communication.js', $relative_to );
				add_js_headline('jQuery(document).ready(function()
				{
					b2evoCommunications.Init({
						dispatcher:"'.$dispatcher.'"
					});
				});' );
				// add translation strings
				T_('Update cancelled', NULL, array( 'for_helper' => true ) );
				T_('Update paused', NULL, array( 'for_helper' => true ) );
				T_('Changes pending', NULL, array( 'for_helper' => true ) );
				T_('Saving changes', NULL, array( 'for_helper' => true ) );
				break;

			case 'colorbox':
				// Colorbox: a lightweight Lightbox alternative -- allows zooming on images and slideshows in groups of images
				// Added by fplanque - (MIT License) - http://colorpowered.com/colorbox/

				global $b2evo_icons_type, $blog;
				$blog_param = empty( $blog ) ? '' : '&blog='.$blog;
				// Colorbox params to translate the strings:
				$colorbox_strings_params = 'current: "{current} / {total}",
					previous: "'.TS_('Previous').'",
					next: "'.TS_('Next').'",
					close: "'.TS_('Close').'",
					openNewWindowText: "'.TS_('Open in a new window').'",
					slideshowStart: "'.TS_('Start slideshow').'",
					slideshowStop: "'.TS_('Stop slideshow').'",';
				// Colorbox params to display a voting panel:
				$colorbox_voting_params = '{'.$colorbox_strings_params.'
					displayVoting: true,
					votingUrl: "'.get_htsrv_url().'anon_async.php?action=voting&vote_type=link&b2evo_icons_type='.$b2evo_icons_type.$blog_param.'",
					minWidth: 320}';
				// Colorbox params without voting panel:
				$colorbox_no_voting_params = '{'.$colorbox_strings_params.'
					minWidth: 255}';

				// Initialize js variables b2evo_colorbox_params* that are used in async loaded colorbox file
				if( is_logged_in() )
				{ // User is logged in
					// All unknown images have a voting panel
					$colorbox_params_other = 'var b2evo_colorbox_params_other = '.$colorbox_voting_params;
					if( is_admin_page() )
					{ // Display a voting panel for all images in backoffice
						$colorbox_params_post = 'var b2evo_colorbox_params_post = '.$colorbox_voting_params;
						$colorbox_params_cmnt = 'var b2evo_colorbox_params_cmnt = '.$colorbox_voting_params;
						$colorbox_params_user = 'var b2evo_colorbox_params_user = '.$colorbox_voting_params;
					}
					else
					{ // Display a voting panel depending on skin settings
						global $Skin;
						if( ! empty( $Skin ) )
						{
							$colorbox_params_post = 'var b2evo_colorbox_params_post = '.( $Skin->get_setting( 'colorbox_vote_post' ) ? $colorbox_voting_params : $colorbox_no_voting_params );
							$colorbox_params_cmnt = 'var b2evo_colorbox_params_cmnt = '.( $Skin->get_setting( 'colorbox_vote_comment' ) ? $colorbox_voting_params : $colorbox_no_voting_params );
							$colorbox_params_user = 'var b2evo_colorbox_params_user = '.( $Skin->get_setting( 'colorbox_vote_user' ) ? $colorbox_voting_params : $colorbox_no_voting_params );
						}
					}
				}
				if( ! isset( $colorbox_params_post ) )
				{ // Don't display a voting panel for all images if user is NOT logged in OR for case when $Skin is not defined
					$colorbox_params_other = 'var b2evo_colorbox_params_other = '.$colorbox_no_voting_params;
					$colorbox_params_post = 'var b2evo_colorbox_params_post = '.$colorbox_no_voting_params;
					$colorbox_params_cmnt = 'var b2evo_colorbox_params_cmnt = '.$colorbox_no_voting_params;
					$colorbox_params_user = 'var b2evo_colorbox_params_user = '.$colorbox_no_voting_params;
				}

				require_js_defer( '#jquery#', $relative_to );
				// Initialize the colorbox settings:
				add_js_footerline(
					// For post images
					$colorbox_params_post.';
					'// For comment images
					.$colorbox_params_cmnt.';
					'// For user images
					.$colorbox_params_user.';
					'// For all other images
					.$colorbox_params_other.';' );
				// TODO: translation strings for colorbox buttons

				// Do NOT require colorbox.bmin.js here because it is grunted in evo_generic.bmin.js:
				// require_js_defer( 'build/colorbox.bmin.js', $relative_to );

				if( is_admin_page() )
				{
					global $AdminUI;
					if( ! empty( $AdminUI ) )
					{
						$colorbox_css_file = $AdminUI->get_template( 'colorbox_css_file' );
					}
				}
				else
				{
					global $Skin;
					if( ! empty( $Skin ) )
					{
						$colorbox_css_file = $Skin->get_template( 'colorbox_css_file' );
					}
				}
				require_css( ( empty( $colorbox_css_file ) ? 'colorbox-regular.min.css' : $colorbox_css_file ), $relative_to );
				break;
		}
		// add to list of loaded helpers
		$helpers[] = $helper;
	}
}

/**
 * Memorize that a specific translation will be required by the current page.
 * All requested translations will be included in the page body only once (when footerlines is called)
 *
 * @param string string, untranslated string
 * @param string translation, translated string
 */
function add_js_translation( $string, $translation )
{
	global $js_translations;
	if( $string != $translation )
	{ // it's translated
		$js_translations[ $string ] = $translation;
	}
}


/**
 * Add a headline, which then gets output in the HTML HEAD section.
 * If you want to include CSS or JavaScript files, please use
 * {@link require_css()} and {@link require_js_async()} and {@link require_js_defer()} instead.
 * This avoids duplicates and allows caching/concatenating those files
 * later (not implemented yet)
 *
 * @param string HTML tag like <script></script> or <link />
 * @param string File name (used to index)
 * @param boolean|string Group headlines by this group in order to allow use files with same names from several places
 * @param integer Insert new headline in the given index, Useful to insert superbundled file instead of first bundled file
 */
function add_headline( $headline, $file_name = NULL, $group_relative_to = '#nogroup#', $file_index = NULL )
{
	if( $file_name === NULL )
	{	// Add inline code:
		global $headline_inline_code;
		$headline_inline_code[] = $headline;
	}
	else
	{	// Add include file:
		global $headline_include_file, $headline_file_index;
		// Convert boolean, NULL and etc. values to string format:
		$group_relative_to = strval( $group_relative_to );
		if( isset( $headline_file_index[ $group_relative_to ][ $file_name ] ) )
		{	// Skip already included file from the same group:
			return;
		}
		if( $file_index === NULL || isset( $headline_include_file[ $file_index ] ) )
		{	// Use auto order/index:
			$headline_include_file[] = $headline;
			$file_index = max( array_keys( $headline_include_file ) );
		}
		else
		{	// Use specific order/index when it is requested and the index is free:
			$headline_include_file[ $file_index ] = $headline;
		}
		// Flag to don't include same file from same group twice,
		// Also store value as index/order in order to dequeue it quickly:
		$headline_file_index[ $group_relative_to ][ $file_name ] = $file_index;
	}
}


/**
 * Add a footerline, which then gets output before the </body> tag.
 * If you want to include CSS or JavaScript files, please use
 * {@link require_css()} and {@link require_js_async()} and {@link require_js_defer()} instead.
 * This avoids duplicates and allows caching/concatenating those files
 * later (not implemented yet)
 *
 * @param string HTML tag like <script></script> or <link />
 * @param string File name (used to index)
 * @param boolean|string Group footerlines by this group in order to allow use files with same names from several places
 * @param integer Insert new headline in the given index, Useful to insert superbundled file instead of first bundled file
 */
function add_footerline( $footerline, $file_name = NULL, $group_relative_to = '#nogroup#', $file_index = NULL )
{
	if( $file_name === NULL )
	{	// Add inline code:
		global $footerline_inline_code;
		$footerline_inline_code[] = $footerline;
	}
	else
	{	// Add include file:
		global $footerline_include_file, $footerline_file_index;
		// Convert boolean, NULL and etc. values to string format:
		$group_relative_to = strval( $group_relative_to );
		if( isset( $footerline_file_index[ $group_relative_to ][ $file_name ] ) )
		{	// Skip already included file from the same group:
			return;
		}
		if( $file_index === NULL || isset( $footerline_include_file[ $file_index ] ) )
		{	// Use auto order/index:
			$footerline_include_file[] = $footerline;
			$file_index = max( array_keys( $footerline_include_file ) );
		}
		else
		{	// Use specific order/index when it is requested and the index is free:
			$footerline_include_file[ $file_index ] = $footerline;
		}
		// Flag to don't include same file from same group twice,
		// Also store value as index/order in order to dequeue it quickly:
		$footerline_file_index[ $group_relative_to ][ $file_name ] = $file_index;
	}
}


/**
 * Add a Javascript headline.
 * This is an extra function, to provide consistent wrapping and allow to bundle it
 * (i.e. create a bundle with all required JS files and these inline code snippets,
 *  in the correct order).
 * @param string Javascript
 */
function add_js_headline($headline)
{
	add_headline("<script>\n\t/* <![CDATA[ */\n\t\t"
		.$headline."\n\t/* ]]> */\n\t</script>");
}


/**
 * Add a Javascript footerline.
 * This is an extra function, to provide consistent wrapping and allow to bundle it
 * (i.e. create a bundle with all required JS files and these inline code snippets,
 *  in the correct order).
 * @param string Javascript
 */
function add_js_footerline($footerline)
{
	add_footerline("<script>\n\t/* <![CDATA[ */\n\t\t"
		.$footerline."\n\t/* ]]> */\n\t</script>");
}


/**
 * Add a CSS headline.
 * This is an extra function, to provide consistent wrapping and allow to bundle it
 * (i.e. create a bundle with all required JS files and these inline code snippets,
 *  in the correct order).
 * @param string CSS
 */
function add_css_headline($headline)
{
	add_headline("<style type=\"text/css\">\n\t".$headline."\n\t</style>");
}


/**
 * Add a CSS footerline.
 * This is an extra function, to provide consistent wrapping and allow to bundle it
 * (i.e. create a bundle with all required CSS code snippets,
 *  in the correct order).
 * @param string CSS
 */
function add_css_footerline($footerline)
{
	add_footerline("<style type=\"text/css\">\n\t".$footerline."\n\t</style>");
}


/**
 * Registers all the javascripts needed by the toolbar menu
 *
 * @deprecated because #evo_toolbar doesn't use js anymore, only css is enough
 */
function add_js_for_toolbar( $relative_to = 'rsc_url' )
{
	return true;
}


/**
 * Registers headlines required by AJAX forms, but only if javascript forms are enabled in blog settings.
 *
 * @deprecated Because we don't need communication.js to work with AJAX forms
 */
function init_ajax_forms( $relative_to = 'blog' )
{
	/*global $Collection, $Blog;

	if( !empty($Blog) && $Blog->get_setting('ajax_form_enabled') )
	{
		require_js_defer( 'communication.js', $relative_to );
	}*/
}


/**
 * Registers headlines required by comments forms
 */
function init_ratings_js( $relative_to = 'blog', $force_init = false )
{
	global $Item;

	// fp> Note, the following test is good for $disp == 'single', not for 'posts'
	if( $force_init || ( !empty($Item) && $Item->can_rate() ) )
	{
		require_js_defer( '#jquery#', $relative_to ); // dependency
		require_js_defer( 'customized:jquery/raty/jquery.raty.min.js', $relative_to );
		require_js_defer( 'src/evo_init_comment_rating.js', $relative_to );
	}
}


/**
 * Registers headlines required to a bubbletip above user login.
 *
 * @param string alias, url or filename (relative to rsc/css, rsc/js) for JS/CSS files
 * @param string Library: 'bubbletip', 'popover'
 */
function init_bubbletip_js( $relative_to = 'rsc_url', $library = 'bubbletip' )
{
	if( ! check_setting( 'bubbletip' ) )
	{ // If setting "bubbletip" is OFF for current case
		return;
	}

	require_js_defer( '#jquery#', $relative_to );

	switch( $library )
	{
		case 'popover':
			// Use popover library of bootstrap
			require_js_defer( 'build/popover.bmin.js', $relative_to );
			break;

		case 'bubbletip':
		default:
			// Use bubbletip plugin of jQuery
			require_js_defer( 'customized:jquery/bubbletip/js/jquery.bubbletip.min.js', $relative_to );
			require_js_defer( 'build/bubbletip.bmin.js', $relative_to );
			require_css( 'customized:jquery/bubbletip/css/jquery.bubbletip.css', $relative_to );
			break;
	}
}


/**
 * Registers headlines required to display a bubbletip to the right of user multi-field.
 *
 * @param string alias, url or filename (relative to rsc/css, rsc/js) for JS/CSS files
 * @param string Library: 'bubbletip', 'popover'
 */
function init_userfields_js( $relative_to = 'rsc_url', $library = 'bubbletip' )
{
	// Load to autocomplete user fields with list type
	require_js_defer( '#jqueryUI#', $relative_to );
	require_css( '#jqueryUI_css#', $relative_to );

	switch( $library )
	{
		case 'popover':
			// Use popover library of bootstrap
			require_js_defer( 'build/popover.bmin.js', $relative_to );
			break;

		case 'bubbletip':
		default:
			// Use bubbletip plugin of jQuery
			require_js_defer( 'customized:jquery/bubbletip/js/jquery.bubbletip.min.js', $relative_to );
			require_js_defer( 'build/bubbletip.bmin.js', $relative_to );
			require_css( 'customized:jquery/bubbletip/css/jquery.bubbletip.css', $relative_to );
			break;
	}
}


/**
 * Registers headlines required to display a bubbletip to the right of plugin help icon.
 *
 * @deprecated Use function init_popover_js()
 *
 * @param string alias, url or filename (relative to rsc/css, rsc/js) for JS/CSS files
 * @param string Library: 'bubbletip', 'popover'
 */
function init_plugins_js( $relative_to = 'rsc_url', $library = 'bubbletip' )
{
	init_popover_js( $relative_to, $library );
}


/**
 * Registers headlines required to display a bubbletip to the right of plugin, widget, custom fields help icon.
 *
 * @param string alias, url or filename (relative to rsc/css, rsc/js) for JS/CSS files
 * @param string Library: 'bubbletip', 'popover'
 */
function init_popover_js( $relative_to = 'rsc_url', $library = 'bubbletip' )
{
	require_js_defer( '#jquery#', $relative_to );

	switch( $library )
	{
		case 'popover':
			// Use popover library of bootstrap
			require_js_defer( 'build/popover.bmin.js', $relative_to );
			break;

		case 'bubbletip':
		default:
			// Use bubbletip plugin of jQuery
			require_js_defer( 'customized:jquery/bubbletip/js/jquery.bubbletip.min.js', $relative_to );
			require_js_defer( 'build/bubbletip.bmin.js', $relative_to );
			require_css( 'customized:jquery/bubbletip/css/jquery.bubbletip.css', $relative_to );
			break;
	}
}


/**
 * Registers headlines for initialization of datepicker inputs
 */
function init_datepicker_js( $relative_to = 'rsc_url' )
{
	require_js_defer( '#jqueryUI#', $relative_to );
	require_css( '#jqueryUI_css#', $relative_to );

	// We did not use json_encode here as it will escape the dateFormat:
	expose_var_to_js( 'evo_init_datepicker', '{'
				.'selector: ".form_date_input",'
				.'config: {'
					.'dateFormat: "'.jquery_datepicker_datefmt().'",'
					.'monthNames: '.jquery_datepicker_month_names().','
					.'dayNamesMin: '.jquery_datepicker_day_names().','
					.'firstDay: '.locale_startofweek().'}'
			.'}' );
}


/**
 * Registers headlines for initialization of jQuery Tokeninput plugin
 */
function init_tokeninput_js( $relative_to = 'rsc_url' )
{
	require_js_defer( '#jquery#', $relative_to ); // dependency
	require_js_defer( 'customized:jquery/tokeninput/js/jquery.tokeninput.js', $relative_to );
	require_css( 'customized:jquery/tokeninput/css/jquery.token-input-facebook.css', $relative_to );
}


/**
 * Registers headlines for initialization of functions to work with Results tables
 */
function init_results_js( $relative_to = 'rsc_url' )
{
	require_js_defer( '#jquery#', $relative_to ); // dependency
	require_js_defer( 'results.js', $relative_to );
}


/**
 * Registers headlines for initialization of functions to work with affixed Messages
 */
function init_affix_messages_js( $offset = NULL )
{
	global $display_mode, $site_Skin;

	if( isset( $display_mode ) && $display_mode == 'js' )
	{	// Don't use affixed Messages in JS mode from modal windows:
		return;
	}

	if( empty( $offset ) && ! ( ( $offset === '0' ) || ( $offset === 0 ) ) )
	{	// This should not include evobar and header height - those will be taken care of by the message affix JS script:
		$offset = 10;
	}

	$site_header_fixed = 'false';
	if( $site_Skin )
	{	// Get fixed header setting to pass to messages affix JS script:
		$site_header_fixed = $site_Skin->get_setting( 'fixed_header' ) == 1 ? 'true' : 'false';
	}

	add_js_headline( 'evo_affix_msg_offset = '.format_to_js( $offset ).'; evo_affix_fixed_header = '.format_to_js( $site_header_fixed ).';' );
	require_js_defer( 'src/evo_init_affix_messages.js', 'blog' );
}


/**
 * Registers headlines for initialization of functions to work a voting panel for comments
 *
 * @param boolean|string Is the file's path relative to the base path/url?
 */
function init_voting_comment_js( $relative_to = 'rsc_url' )
{
	global $Collection, $Blog, $b2evo_icons_type;

	if( empty( $Blog ) || ! is_logged_in( false ) || ! $Blog->get_setting('allow_rating_comment_helpfulness') )
	{	// If User is not logged OR Users cannot vote
		return false;
	}

	require_js_defer( '#jquery#', $relative_to ); // dependency
	require_js_defer( 'voting.js', $relative_to );

	$js_config = array(
		'action_url' => url_add_param( get_htsrv_url().'anon_async.php', array(
					'action'           => 'voting',
					'vote_type'        => 'comment',
					'b2evo_icons_type' => $b2evo_icons_type
			) ),
		);

	expose_var_to_js( 'evo_init_comment_voting_config', evo_json_encode( $js_config ) );
}


/**
 * Registers headlines for initialization of functions to work a voting panel for items
 *
 * @param boolean|string Is the file's path relative to the base path/url?
 */
function init_voting_item_js( $relative_to = 'rsc_url' )
{
	global $Collection, $Blog, $b2evo_icons_type;

	if( empty( $Blog ) || ! is_logged_in( false ) || ! $Blog->get_setting( 'voting_positive' ) )
	{	// If User is not logged OR Users cannot vote:
		return false;
	}

	require_js_defer( '#jquery#', $relative_to );
	require_js_defer( 'voting.js', $relative_to );

	$js_config = array(
		'action_url' => url_add_param( get_htsrv_url().'anon_async.php', array(
				'action'           => 'voting',
				'vote_type'        => 'item',
				'b2evo_icons_type' => $b2evo_icons_type
			) ),
		);

	expose_var_to_js( 'evo_init_item_voting_config', evo_json_encode( $js_config ) );
}


/**
 * Registers headlines for initialization of colorpicker inputs
 */
function init_colorpicker_js( $relative_to = 'rsc_url' )
{
	if( ! is_logged_in() )
	{	// Colorpicker is initialized only for logged in users:
		return;
	}

	global $current_User, $UserSettings;

	require_js_defer( '#jquery#', $relative_to );
	require_js_defer( 'ext:bootstrap/colorpicker/js/bootstrap-colorpicker.min.js', $relative_to );
	require_css( 'ext:bootstrap/colorpicker/css/bootstrap-colorpicker.min.css', $relative_to );

	// Get preselected colors from settings of current User:
	$user_colors = $UserSettings->get( 'colorpicker', $current_User->ID );
	if( empty( $user_colors ) )
	{	// Use these default colors if user didn't selected any color yet:
		$user_colors = array(
			'black'   => '#000000',
			'grey'    => '#999999',
			'white'   => '#ffffff',
			'red'     => '#FF0000',
			'default' => '#777777',
			'primary' => '#337ab7',
			'success' => '#5cb85c',
			'info'    => '#5bc0de',
			'warning' => '#f0ad4e',
			'danger'  => '#d9534f'
		);
	}
	else
	{	// Convert user colors to array:
		$user_colors = explode( ';', $user_colors );
	}

	add_js_headline( 'jQuery( document ).ready( function() { evo_initialize_colorpicker_inputs() } );
function evo_initialize_colorpicker_inputs()
{
	jQuery( ".form_color_input" ).each( function()
	{
		var predefined_colors = ["'.implode( '","', $user_colors ).'"];
		var colored_input = jQuery( this );
		colored_input.parent().colorpicker( {
			format: jQuery( this ).hasClass( "form_color_transparent" ) ? false : "hex",
			colorSelectors: predefined_colors
		} )
		.on( "changeColor", function()
		{
			if( typeof( parent.evo_customizer_update_style ) == "function" )
			{	// Update style in designer customizer mode if it is enabled currently:
				parent.evo_customizer_update_style( colored_input );
			}
		} )
		.on( "hidePicker", function( e )
		{	// Update predefined colors with new last selected:
			var current_colors = e.color.predefinedColors;
			var new_colors = current_colors.slice();
			var new_color = e.color.toString();
			var new_color_index = new_colors.indexOf( new_color );
			if( new_color_index > -1 )
			{	// Remove a duplicated color:
				new_colors.splice( new_color_index, 1 );
			}
			new_colors.unshift( new_color );
			new_colors.splice( 10, 1 );

			if( new_colors.join( "" ) == current_colors.join( "" ) )
			{	// Dont update if colors is not changed:
				return;
			}

			// Reinitialize colorpickers with new preselected colors:
			jQuery( ".form_color_input" ).each( function()
			{
				var colored_input = jQuery( this ).parent();
				colored_input.colorpicker( "destroy" );
				colored_input.colorpicker( {
					format: jQuery( this ).hasClass( "form_color_transparent" ) ? false : "hex",
					colorSelectors: new_colors
				} );
			} );

			// Save new colors in settings for current User:
			jQuery.ajax(
			{
				type: "POST",
				url: htsrv_url + "anon_async.php",
				data: {
					"action": "colorpicker",
					"colors": new_colors.join( ";" ),
					"crumb_colorpicker": "'.get_crumb( 'colorpicker' ).'"
				}
			} );
		} );
	} );
}' );
}


/**
 * Registers headlines required to autocomplete the user logins
 *
 * @param string alias, url or filename (relative to rsc/css, rsc/js) for JS/CSS files
 * @param string Library: 'hintbox', 'typeahead'
 */
function init_autocomplete_login_js( $relative_to = 'rsc_url', $library = 'hintbox' )
{
	global $Collection, $Blog;

	require_js_defer( '#jquery#', $relative_to ); // dependency

	switch( $library )
	{
		case 'typeahead':
			// Use typeahead library of bootstrap
			require_js_defer( '#bootstrap_typeahead#', $relative_to );
			expose_var_to_js( 'evo_autocomplete_login_config', '{
					url: "'.( isset( $Blog ) ? 'collections/'.$Blog->get( 'urlname' ).'/assignees' : 'users/logins' ).'",
					selector: "input.autocomplete_login",
				}' );
			break;

		case 'hintbox':
		default:
			// Use hintbox plugin of jQuery

			// Add jQuery hintbox (autocompletion).
			// Form 'username' field requires the following JS and CSS.
			// fp> TODO: think about a way to bundle this with other JS on the page -- maybe always load hintbox in the backoffice
			//     dh> Handle it via http://www.appelsiini.net/projects/lazyload ?
			// dh> TODO: should probably also get ported to use jquery.ui.autocomplete (or its successor)
			require_css( 'ext:jquery/hintbox/css/jquery.hintbox.css', $relative_to );
			require_js( 'ext:jquery/hintbox/js/jquery.hintbox.min.js', $relative_to );
			add_js_headline( 'jQuery( document ).on( "focus", "input.autocomplete_login", function()
			{
				var ajax_url = "";
				if( jQuery( this ).hasClass( "only_assignees" ) )
				{
					ajax_url = restapi_url + "'.( isset( $Blog ) ? 'collections/'.$Blog->get( 'urlname' ).'/assignees' : 'users/logins' ).'";
				}
				else
				{
					ajax_url = restapi_url + "users/logins";
				}
				if( jQuery( this ).data( "status" ) )
				{
					ajax_url += "&status=" + jQuery( this ).data( "status" );
				}
				jQuery( this ).hintbox(
				{
					url: ajax_url,
					matchHint: true,
					autoDimentions: true,
					json: true,
				} );
				'
				// Don't submit a form by Enter when user is editing the owner fields
				.get_prevent_key_enter_js( 'input.autocomplete_login' ).'
			} );' );
			break;
	}
}


/**
 * Registers headlines required to jqPlot charts
 *
 * @param string alias, url or filename (relative to rsc/css, rsc/js) for JS/CSS files
 * @param boolean TRUE to print script tag on the page, FALSE to store in array to print then inside <head>
 * @param string Version number to append at the end of requested url to avoid getting an old version from the cache
 * @param string Position where the CSS files will be inserted, either 'headlines' (inside <head>) or 'footerlines' (before </body>)
 */
function init_jqplot_js( $relative_to = 'rsc_url', $output = false, $version = '#', $position = 'headlines' )
{
	require_js_defer( 'build/evo_jqplot.bmin.js', $relative_to, $output, $version, $position );
	require_js_defer( 'src/evo_init_canvas_bar_chart.js', $relative_to, $output, $version, $position );
	require_css_async( 'b2evo_jqplot.bmin.css', $relative_to, NULL, NULL, $version, $output, $position );
}


/**
 * Initialize JavaScript variables for jQuery QueryBuilder plugin
 */
function init_querybuilder_js( $relative_to = 'rsc_url' )
{
	global $current_locale;

	require_js_defer( '#jquery#', $relative_to ); // dependency
	require_css( '#jqueryUI_css#', $relative_to ); // dependency for date picker
	require_js_defer( 'ext:jquery/query-builder/js/doT.min.js', $relative_to ); // dependency
	require_js_defer( 'ext:jquery/query-builder/js/jquery.extendext.min.js', $relative_to ); // dependency
	require_js_defer( 'ext:jquery/query-builder/js/moment.js', $relative_to ); // dependency

	require_js_defer( 'ext:jquery/query-builder/js/query-builder.min.js', $relative_to );
	require_css( 'ext:jquery/query-builder/css/jquery.query-builder.default.css', $relative_to );

	// Load language file if such file exists:
	$query_builder_langs = array( 'ar', 'az', 'bg', 'cs', 'da', 'de', 'el', 'en', 'es', 'fa-IR', 'fr', 'he', 'it', 'nl', 'no', 'pl', 'pt-BR', 'pt-PT', 'ro', 'ru', 'sq', 'tr', 'ua', 'zh-CN' );
	if( in_array( $current_locale, $query_builder_langs ) )
	{	// Load lang by full locale name like "en-US":
		require_js_defer( 'ext:jquery/query-builder/js/i18n/query-builder.'.$current_locale.'.js', $relative_to );
	}
	elseif( in_array( substr( $current_locale, 0, 2 ), $query_builder_langs ) )
	{	// Load lang by locale code like "en":
		require_js_defer( 'ext:jquery/query-builder/js/i18n/query-builder.'.substr( $current_locale, 0, 2 ).'.js', $relative_to );
	}
}


/**
 * Initialize Hotkeys library
 */
function init_hotkeys_js( $relative_to = 'rsc_url', $hotkeys = array(), $top_hotkeys = array() )
{
	require_js_defer( '#jquery#', $relative_to ); // dependency
	require_js_defer( '#hotkeys#', $relative_to );

	add_js_headline( 'var shortcut_keys = '.json_encode( $hotkeys ).';' );
	add_js_headline( 'var top_shortcut_keys = '.json_encode( $top_hotkeys ).';' );

	require_js_defer( 'ext:hotkeys/hotkeys.init.js', $relative_to );
}


/**
 * Outputs the collected HTML HEAD lines.
 * @see add_headline()
 * @return string
 */
function include_headlines()
{
	global $headline_include_file, $headline_file_index, $headline_inline_code;

	$all_headlines = array();
	if( is_array( $headline_include_file ) )
	{	// Include files:
		// Sort because after dequeued bundled files we need to move main superbundled file on proper order:
		ksort( $headline_include_file );
		$all_headlines = $headline_include_file;
		unset( $headline_include_file, $headline_file_index );
	}
	if( is_array( $headline_inline_code ) )
	{	// Include inline codes:
		$all_headlines = array_merge( $all_headlines, $headline_inline_code );
		unset( $headline_inline_code );
	}

	if( ! empty( $all_headlines ) )
	{	// Include headlines only if at least one headline is found in any group:
		echo "\n\t<!-- headlines: -->\n\t".implode( "\n\t", $all_headlines );
		echo "\n\n";
	}
}


/**
 * Outputs the collected translation lines before </body>
 *
 * yabs > Should this be expanded to similar functionality to headlines?
 *
 * @see add_js_translation()
 */
function include_footerlines()
{
	global $js_translations, $footerline_include_file, $footerline_file_index, $footerline_inline_code;

	if( !empty( $js_translations ) )
	{
		$r = '';

		foreach( $js_translations as $string => $translation )
		{ // output each translation
			if( $string != $translation )
			{ // this is translated
				$r .= '<div><span class="b2evo_t_string">'.$string.'</span><span class="b2evo_translation">'.$translation.'</span></div>'."\n";
			}
		}
		if( $r )
		{ // we have some translations
			echo '<div id="b2evo_translations" style="display:none;">'."\n";
			echo $r;
			echo '</div>'."\n";
		}
	}

	$all_footerlines = array();
	if( is_array( $footerline_include_file ) )
	{	// Include files:
		$all_footerlines = $footerline_include_file;
		// Sort because after dequeued bundled files we need to move main superbundled file on proper order:
		ksort( $footerline_include_file );
		unset( $footerline_include_file, $footerline_file_index );
	}
	if( is_array( $footerline_inline_code ) )
	{	// Include inline codes:
		$all_footerlines = array_merge( $all_footerlines, $footerline_inline_code );
		unset( $footerline_inline_code );
	}

	if( ! empty( $all_footerlines ) )
	{	// Include footerlines only if at least one footerline is found in any group:
		echo "\n\t<!-- footerlines: -->\n\t".implode( "\n\t", $all_footerlines );
		echo "\n\n";
	}
}


/**
 * Template tag.
 */
function app_version()
{
	global $app_version;
	echo $app_version;
}


/**
 * Displays an empty or a full bullet based on boolean
 *
 * @param boolean true for full bullet, false for empty bullet
 */
function bullet( $bool )
{
	if( $bool )
		return get_icon( 'bullet_full', 'imgtag' );

	return get_icon( 'bullet_empty', 'imgtag' );
}


/**
 * Stub: Links to previous and next post in single post mode
 */
function item_prevnext_links( $params = array() )
{
	global $disp, $MainList;

	if( ! isset( $MainList ) || ! $MainList->single_post || $disp == 'download' )
	{	// Don't display the links in NOT single post mode and on download page:
		return;
	}

	$params = array_merge( array( 'target_blog' => 'auto' ), $params );

	$MainList->prevnext_item_links( $params );
}


/**
 * Stub: Links to previous and next user in single user mode
 */
function user_prevnext_links( $params = array() )
{
	global $UserList, $AdminUI, $Skin;

	$params = array_merge( array(
			'template'     => '$prev$$back$$next$',
			'block_start'  => '<table class="prevnext_user"><tr>',
			'prev_start'   => '<td width="33%">',
			'prev_end'     => '</td>',
			'prev_no_user' => '<td width="33%">&nbsp;</td>',
			'back_start'   => '<td width="33%" class="back_users_list">',
			'back_end'     => '</td>',
			'next_start'   => '<td width="33%" class="right">',
			'next_end'     => '</td>',
			'next_no_user' => '<td width="33%">&nbsp;</td>',
			'block_end'    => '</tr></table>',
			'user_tab'     => 'profile',
		), $params );

	if( !empty( $AdminUI ) )
	{ // Set template from AdminUI
		$user_navigation = $AdminUI->get_template( 'user_navigation' );
	}
	elseif( !empty( $Skin ) )
	{ // Set template from Skin
		$user_navigation = $Skin->get_template( 'user_navigation' );
	}
	if( !empty( $user_navigation ) && is_array( $user_navigation ) )
	{
		$params = array_merge( $params, $user_navigation );
	}

	if( isset($UserList) )
	{
		$UserList->prevnext_user_links( $params );
	}
}


/**
 * Stub
 */
function messages( $params = array() )
{
	global $Messages;

	if( isset( $params['has_errors'] ) )
	{
		$params['has_errors'] = $Messages->has_errors();
	}
	$Messages->disp( $params['block_start'], $params['block_end'] );
}


/**
 * Stub: Links to list pages:
 *
 * @param array Params
 * @param object ItemList2, NULL to use global $MainList
 */
function mainlist_page_links( $params = array(), $ItemList2 = NULL )
{
	if( is_null( $ItemList2 ) )
	{
		global $MainList;
		$ItemList2 = $MainList;
	}

	if( ! empty( $ItemList2 ) )
	{
		$ItemList2->page_links( $params );
	}
}


/**
 * Stub
 *
 * Sets $Item ion global scope
 *
 * @return Item
 */
function & mainlist_get_item()
{
	global $MainList, $featured_displayed_item_IDs;

	if( isset( $MainList ) )
	{
		$Item = & $MainList->get_item();

		if( $Item && in_array( $Item->ID, $featured_displayed_item_IDs ) )
		{ // This post was already displayed as a Featured post, let's skip it and get the next one:
			$Item = & mainlist_get_item();
		}
	}
	else
	{
		$Item = NULL;
	}

	// Make this available globally:
	$GLOBALS['Item'] = & $Item;

	return $Item;
}


/**
 * Stub
 *
 * @return boolean true if empty MainList
 */
function display_if_empty( $params = array() )
{
	global $MainList, $featured_displayed_item_IDs;

	if( isset( $MainList ) && empty( $featured_displayed_item_IDs ) )
	{
		return $MainList->display_if_empty( $params );
	}

	return NULL;
}


/**
 * Check if current page is a single Item's page
 *
 * @param integer|NULL ID of Item to be sure we currently see single page of the Item, NULL - to don't check
 * @return boolean
 */
function is_single_page( $check_item_ID = NULL )
{
	global $disp, $MainList, $Item;

	return ( $disp == 'single' || $disp == 'page' ) &&
		( isset( $MainList ) && $MainList->single_post ) &&
		( isset( $Item ) && $Item->ID > 0 ) &&
		( $check_item_ID === NULL || $check_item_ID == $Item->ID );
}


/**
 * Template tag for credits
 *
 * Note: You can limit (and even disable) the number of links being displayed here though the Admin interface:
 * Blog Settings > Advanced > Software credits
 *
 * @param array
 */
function credits( $params = array() )
{
	/**
	 * @var AbstractSettings
	 */
	global $global_Cache;
	global $Collection, $Blog;

	// Make sure we are not missing any param:
	$params = array_merge( array(
			'list_start'  => ' ',
			'list_end'    => ' ',
			'item_start'  => ' ',
			'item_end'    => ' ',
			'separator'   => ',',
			'after_item'  => '#',
		), $params );


	$cred_links = $global_Cache->getx( 'creds' );
	if( empty( $cred_links ) )
	{	// Use basic default:
		$cred_links = unserialize('a:2:{i:0;a:2:{i:0;s:24:"http://b2evolution.net/r";i:1;s:3:"CMS";}i:1;a:2:{i:0;s:36:"http://b2evolution.net/web-hosting/r";i:1;s:19:"quality web hosting";}}');
	}

	$max_credits = (empty($Blog) ? NULL : $Blog->get_setting( 'max_footer_credits' ));

	display_list( $cred_links, $params['list_start'], $params['list_end'], $params['separator'], $params['item_start'], $params['item_end'], NULL, $max_credits );

	return $max_credits;
}


/**
 * Display rating as 5 stars
 *
 * @param integer Number of stars
 * @param string Class name
 */
function star_rating( $stars, $class = 'not-used-any-more' )
{
	echo get_star_rating( $stars );
}


/**
 * Display "powered by b2evolution" logo
 */
function powered_by( $params = array() )
{
	global $global_Cache, $rsc_uri, $Blog;

	if( isset( $Blog ) &&
	    ( $Blog instanceof Blog ) &&
	    is_pro() &&
	    $Blog->get_setting( 'powered_by_logos' ) )
	{	// Don't display "Powered by b2evolution" logos if it is desabled on PRO version:
		return;
	}

	// Make sure we are not missing any param:
	$params = array_merge( array(
			'block_start' => '<div class="powered_by">',
			'block_end'   => '</div>',
			'img_url'     => '$rsc$img/powered-by-b2evolution-120t.gif',
			'img_width'   => '',
			'img_height'  => '',
		), $params );

	echo $params['block_start'];

	$img_url = str_replace( '$rsc$', $rsc_uri, $params['img_url'] );

	$evo_links = $global_Cache->getx( 'evo_links' );
	if( empty( $evo_links ) )
	{	// Use basic default:
		$evo_links = unserialize('a:1:{s:0:"";a:1:{i:0;a:3:{i:0;i:100;i:1;s:23:"http://b2evolution.net/";i:2;a:2:{i:0;a:2:{i:0;i:55;i:1;s:26:"powered by b2evolution CMS";}i:1;a:2:{i:0;i:100;i:1;s:29:"powered by an open-source CMS";}}}}}');
	}

	echo resolve_link_params( $evo_links, NULL, array(
			'type'        => 'img',
			'img_url'     => $img_url,
			'img_width'   => $params['img_width'],
			'img_height'  => $params['img_height'],
			'title'       => 'b2evolution CMS',
		) );

	echo $params['block_end'];
}



/**
 * DEPRECATED
 */
function bloginfo( $what )
{
	global $Collection, $Blog;
	$Blog->disp( $what );
}

/**
 * Display allowed tags for comments
 * (Mainly provided for WP compatibility. Not recommended for use)
 *
 * @param string format
 */
function comment_allowed_tags( $format = 'htmlbody' )
{
	global $comment_allowed_tags;

	echo format_to_output( $comment_allowed_tags, $format );
}

/**
 * DEPRECATED
 */
function link_pages()
{
	echo '<!-- link_pages() is DEPRECATED -->';
}


/**
 * Return a formatted percentage (should probably go to _misc.funcs)
 */
function percentage( $hit_count, $hit_total, $decimals = 1, $dec_point = '.' )
{
	$percentage = $hit_total > 0 ? $hit_count * 100 / $hit_total : 0;
	return number_format( $percentage, $decimals, $dec_point, '' ).'&nbsp;%';
}

function addup_percentage( $hit_count, $hit_total, $decimals = 1, $dec_point = '.' )
{
	static $addup = 0;

	$addup += $hit_count;
	return number_format( $addup * 100 / $hit_total, $decimals, $dec_point, '' ).'&nbsp;%';
}


/**
 * Check if the array given as the first param contains recursion
 *
 * @param array what to check
 * @param array contains object which were already seen
 * @return boolean true if contains recursion false otherwise
 */
function is_recursive( /*array*/ & $array, /*array*/ & $alreadySeen = array() )
{
    static $uniqueObject;
    if( !$uniqueObject )
    {
        $uniqueObject = new stdClass;
    }

    // Set main array as already seen
    $alreadySeen[] = & $array;

    foreach( $array as & $item )
    { // for each item in array
        if( !is_array( $item ) )
        { // if not array, we don't have to check it
            continue;
        }

        // put the unique object into the end of the array
        $item[] = $uniqueObject;
        $recursionDetected = false;
        foreach( $alreadySeen as $candidate )
        {
            if( end( $candidate ) === $uniqueObject )
            { // In the end of an already scanned array is the same unique Obect, this means that recursion was detected
                $recursionDetected = true;
                break;
            }
        }

        array_pop( $item );

        if( $recursionDetected || is_recursive( $item, $alreadySeen ) )
        { // Check until recursion detected or there are not more arrays
            return true;
        }
    }

    return false;
}


/**
 * Display a form (like comment or contact form) through an ajax call
 *
 * @param array params
 */
function display_ajax_form( $params )
{
	global $rsc_url, $ajax_form_number, $required_js, $b2evo_icons_type;

	if( is_recursive( $params ) )
	{	// The params array contains recursion, don't try to encode, display error message instead
		// We don't use translation because this situation should not really happen ( Probably it happesn with some wrong skin )
		echo '<p style="color:red;font-weight:bold">'.T_( 'This section can\'t be displayed because wrong params were created by the skin.' ).'</p>';
		return;
	}

	// Set icons type to display correct icons on bootstrap skins:
	$params['b2evo_icons_type'] = $b2evo_icons_type;

	if( ! empty( $required_js ) )
	{	// Send all loaded JS files to ajax request in order to don't load them twice:
		// yura: It was done because JS of bootstrap modal doesn't work when jquery JS file is loaded twice.
		$params['required_js'] = $required_js;
	}

	if( ! isset( $params['b2evo_icons_type'] ) && isset( $b2evo_icons_type ) )
	{	// Send current icons type to display proper icons on the loaded AJAX form:
		$params['b2evo_icons_type'] = $b2evo_icons_type;
	}

	if( empty( $ajax_form_number ) )
	{	// Set number for ajax form to use unique ID for each new form
		$ajax_form_number = 0;
	}
	$ajax_form_number++;

	echo '<div id="ajax_form_number_'.$ajax_form_number.'" class="section_requires_javascript">';

	if( isset( $params['action'], $params['p'] ) && $params['action'] == 'get_comment_form' )
	{	// Display anchor here even form is not loaded yet because it is used e.g. for reply links:
		$comment_form_anchor = empty( $params['params']['comment_form_anchor'] ) ? 'form_p' : $params['params']['comment_form_anchor'];
		echo '<a id="'.format_to_output( $comment_form_anchor.$params['p'], 'htmlattr' ).'"></a>';
	}

	// Display loader gif until the ajax call returns:
	echo '<p class="ajax-loader"><span class="loader_img loader_ajax_form" title="'.T_('Loading...').'"></span><br />'.T_( 'Form is loading...' ).'</p>';
	$ajax_form_config = array(
			'form_number' => $ajax_form_number,
			'json_params' => $params,
			'load_ajax_form_on_page_load' => !empty( $params['params']['load_ajax_form_on_page_load'] ),
		);
	expose_var_to_js( 'ajax_form_'.$ajax_form_number, $ajax_form_config, 'evo_ajax_form_config' );
	?>
	<noscript>
		<?php echo '<p>'.T_( 'This section can only be displayed by javascript enabled browsers.' ).'</p>'; ?>
	</noscript>
	<?php
	echo '</div>';
}


/**
 * Display login form
 *
 * @param array params
 */
function display_login_form( $params )
{
	global $Settings, $Plugins, $Session, $Collection, $Blog, $blog, $dummy_fields;
	global $admin_url, $baseurl, $ReqHost, $redirect_to;

	$params = array_merge( array(
			'form_before' => '',
			'form_after' => '',
			'form_action' => '',
			'form_name' => 'login_form',
			'form_title' => '',
			'form_layout' => '',
			'form_class' => 'bComment',
			'source' => 'inskin login form',
			'inskin' => true,
			'inskin_urls' => true, // Use urls of front-end
			'login_required' => true,
			'validate_required' => NULL,
			'redirect_to' => '',
			'return_to' => '',
			'login' => '',
			'action' => '',
			'reqID' => '',
			'sessID' => '',
			'transmit_hashed_password' => false,
			'get_widget_login_hidden_fields' => false,
			'display_abort_link'  => true,
			'abort_link_position' => 'above_form', // 'above_form', 'form_title'
			'abort_link_text'     => T_('Abort login!'),
			'login_button_text'     => T_('Log in!'),
			'login_button_class'    => 'btn btn-success btn-lg',
			'display_lostpass_link' => true,
			'lostpass_link_text'    => T_('Lost your password?'),
			'lostpass_link_class'   => '',
			'display_reg_link'      => false, // Display registration link after login button
			'reg_link_text'         => T_('Register').' &raquo;',
			'reg_link_class'        => 'btn btn-primary btn-lg pull-right',
		), $params );

	$inskin = $params['inskin'];
	$login = $params['login'];
	$redirect_to = $params['redirect_to'];
	$return_to = $params['return_to'];
	$links = array();
	$form_links = array();

	if( $params['display_abort_link']
		&& empty( $params['login_required'] )
		&& $params['action'] != 'req_activate_email'
		&& strpos( $return_to, $admin_url ) !== 0
		&& strpos( $ReqHost.$return_to, $admin_url ) !== 0 )
	{ // No login required, allow to pass through
		// TODO: dh> validate return_to param?!
		// check if return_to url requires logged in user
		if( empty( $return_to ) || require_login( $return_to, true ) )
		{ // logged in user require for return_to url
			if( !empty( $blog ) )
			{ // blog is set
				if( empty( $Blog ) )
				{
					$BlogCache = & get_BlogCache();
					$Collection = $Blog = $BlogCache->get_by_ID( $blog, false );
				}
				// set abort url to Blog url
				$abort_url = $Blog->gen_blogurl();
			}
			else
			{ // set abort login url to base url
				$abort_url = $baseurl;
			}
		}
		else
		{ // logged in user isn't required for return_to url, set abort url to return_to
			$abort_url = $return_to;
		}
		// Gets displayed as link to the location on the login form if no login is required
		$abort_link = '<a href="'.htmlspecialchars( url_rel_to_same_host( $abort_url, $ReqHost ) ).'">'.$params['abort_link_text'].'</a>';
		if( $params['abort_link_position'] == 'above_form' )
		{ // Display an abort link under login form
			$links[] = $abort_link;
		}
		elseif( $params['abort_link_position'] == 'form_title' )
		{ // Display an abort link in form title block
			$form_links[] = $abort_link;
		}
	}

	if( ! $inskin && is_logged_in() )
	{ // if we arrive here, but are logged in, provide an option to logout (e.g. during the email validation procedure)
		$links[] = get_user_logout_link();
	}

	if( count( $links ) )
	{
		echo '<div class="evo_form__login_links">'
				.'<div class="floatright">'.implode( ' &middot; ', $links ).'</div>'
				.'<div class="clear"></div>'
			.'</div>';
	}

	$form_links = count( $form_links ) ? '<span class="pull-right">'.implode( ' ', $form_links ).'</span>' : '';
	echo str_replace( '$form_links$', $form_links, $params['form_before'] );

	$Form = new Form( $params['form_action'] , $params['form_name'], 'post', $params['form_layout'] );

	$Form->begin_form( $params['form_class'] );

	$Form->add_crumb( 'loginform' );
	$source = param( 'source', 'string', $params['source'].' login form' );
	if( ! empty( $blog ) )
	{
		$Form->hidden( 'blog', $blog );
	}
	$Form->hidden( 'source', $source );
	$Form->hidden( 'redirect_to', $redirect_to );
	$Form->hidden( 'return_to', $return_to );
	if( $inskin || $params['inskin_urls'] )
	{ // inskin login form
		$Form->hidden( 'inskin', true );
		$separator = '<br />';
	}
	else
	{ // basic login form

		if( ! empty( $params['form_title'] ) )
		{
			echo '<h4>'.$params['form_title'].'</h4>';
		}

		$Form->hidden( 'validate_required', $params[ 'validate_required' ] );
		if( isset( $params[ 'action' ],  $params[ 'reqID' ], $params[ 'sessID' ] ) &&  $params[ 'action' ] == 'activateacc_sec' )
		{ // the user clicked the link from the "validate your account" email, but has not been logged in; pass on the relevant data:
			$Form->hidden( 'action', 'activateacc_sec' );
			$Form->hidden( 'reqID', $params[ 'reqID' ] );
			$Form->hidden( 'sessID', $params[ 'sessID' ] );
		}
		$separator = '';
	}

	// check if should transmit hashed password
	if( $params[ 'transmit_hashed_password' ] )
	{ // used by JS-password encryption/hashing:
		$pepper = $Session->get( 'core.pepper' );
		if( empty( $pepper ) )
		{ // Do not regenerate if already set because we want to reuse the previous salt on login screen reloads
			// fp> Question: the comment implies that the salt is reset even on failed login attemps. Why that? I would only have reset it on successful login. Do experts recommend it this way?
			// but if you kill the session you get a new salt anyway, so it's no big deal.
			// At that point, why not reset the salt at every reload? (it may be good to keep it, but I think the reason should be documented here)
			$pepper = generate_random_key(64);
			$Session->set( 'core.pepper', $pepper, 86400 /* expire in 1 day */ );
			$Session->dbsave(); // save now, in case there's an error later, and not saving it would prevent the user from logging in.
		}
		$Form->hidden( 'pepper', $pepper );
		// Add container for the hashed password hidden inputs
		echo '<div id="pwd_hashed_container"></div>'; // gets filled by JS
	}

	if( $inskin )
	{
		$Form->login_input( $dummy_fields['login'], $params['login'], 18, /* TRANS: noun */ T_('Login'), $separator.T_('Enter your username (or email address).'),
					array( 'maxlength' => 255, 'required' => true ) );
	}
	else
	{
		$Form->login_input( $dummy_fields[ 'login' ], $params[ 'login' ], 18, '', '',
					array( 'maxlength' => 255, 'required' => true, 'placeholder' => T_('Username (or email address)') ) );
	}

	if( $params['display_lostpass_link'] )
	{	// Display a lost password link:
		$lost_password_url = get_lostpassword_url( $redirect_to, '&amp;', $return_to );
		if( ! empty( $login ) )
		{	// Append login to the lost password form url:
			$lost_password_url = url_add_param( $lost_password_url, $dummy_fields['login'].'='.rawurlencode( $login ) );
		}
		$lost_password_link = '<a href="'.$lost_password_url.'"'
			.( empty( $params['lostpass_link_text'] ) ? '' : ' class="'.format_to_output( $params['lostpass_link_class'], 'htmlattr' ).'"' ).'>'
			.$params['lostpass_link_text'].'</a>';
	}
	else
	{	// Don't display a lost password link:
		$lost_password_link = '';
	}

	if( $inskin )
	{
		$Form->begin_field();
		$Form->password_input( $dummy_fields[ 'pwd' ], '', 18, T_('Password'), array( 'note' => $lost_password_link, 'maxlength' => 70, 'class' => 'input_text', 'required' => true ) );
		$Form->end_field();
	}
	else
	{
		$Form->password_input( $dummy_fields[ 'pwd' ], '', 18, '', array( 'placeholder' => T_('Password'), 'note' => $lost_password_link, 'maxlength' => 70, 'class' => 'input_text', 'input_required' => 'required' ) );
	}

	// Allow a plugin to add fields/payload
	$Plugins->trigger_event( 'DisplayLoginFormFieldset', array( 'Form' => & $Form ) );

	// Display registration link after login button
	$register_link = $params['display_reg_link'] ? get_user_register_link( '', '', $params['reg_link_text'], '#', true /*disp_when_logged_in*/, $redirect_to, $source, $params['reg_link_class'] ) : '';

	// Submit button(s):
	$submit_buttons = array( array( 'name' => 'login_action[login]', 'value' => $params['login_button_text'], 'class' => $params['login_button_class'], 'input_suffix' => $register_link ) );

	$Form->buttons_input( $submit_buttons );

	if( $inskin )
	{
		$before_register_link = '<div class="login_actions" style="text-align:right; margin: 1em 0 1ex"><strong>';
		$after_register_link = '</strong></div>';
		user_register_link( $before_register_link, $after_register_link, T_('No account yet? Register here').' &raquo;', '#', true /*disp_when_logged_in*/, $redirect_to, $source );
	}
	else
	{
		// Passthrough REQUEST data (when login is required after having POSTed something)
		// (Exclusion of 'login_action', 'login', and 'action' has been removed. This should get handled via detection in Form (included_input_field_names),
		//  and "action" is protected via crumbs)
		$Form->hiddens_by_key( $_REQUEST, array( 'pwd_hashed', 'submit' ) );
	}

	$Form->end_form();

	echo $params['form_after'];

	display_login_js_handler( $params );
}


/**
 * Display footer under login form
 *
 * @param array Params
 */
function display_login_form_footer( $params = array() )
{
	global $Hit;

	$params = array_merge( array(
			'login_link_class' => 'evo_login_dialog_standard_link',
			'ip_address_class' => 'evo_login_dialog_footer text-muted',
			'source'           => NULL,
			'redirect_to'      => NULL,
			'return_to'        => NULL,
		), $params );

	if( $params['source'] === NULL )
	{	// Default source:
		$params['source'] = param( 'source', 'string', 'inskin login form' );
	}

	if( $params['redirect_to'] === NULL )
	{	// Default redirect URL:
		$params['redirect_to'] = param( 'redirect_to', 'url', '' );
	}

	if( $params['return_to'] === NULL )
	{	// Default return URL:
		$params['return_to'] = param( 'return_to', 'url', '' );
	}

	echo '<div class="'.$params['login_link_class'].'">'
			.'<a href="'.get_htsrv_url( 'login' ).'login.php?source='.rawurlencode( $params['source'] ).'&amp;redirect_to='.rawurlencode( $params['redirect_to'] ).'&amp;return_to='.rawurlencode( $params['return_to'] ).'">'
				.T_('Use basic login form instead').' &raquo;'
			.'</a>'
		.'</div>';

	echo '<div class="'.$params['ip_address_class'].'">'
			.sprintf( T_('Your IP address: %s'), $Hit->IP )
		.'</div>';
}


/**
 * Display the login form js part, to get the user salt and encrypt the password
 *
 * @param array params
 */
function display_login_js_handler( $params )
{
	global $dummy_fields, $Session;

	$params = array_merge( array( 'get_widget_login_hidden_fields' => false ), $params );

	$login_handler_config = array(
			'dummy_field_login'                     => $dummy_fields['login'],
			'dummy_field_pwd'                       => $dummy_fields['pwd'],
			'params_get_widget_login_hidden_fields' => $params['get_widget_login_hidden_fields'],
			'params_transmit_hashed_password'       => $params['transmit_hashed_password'],
			'session_ID'                            => $Session->ID,
			'crumb_loginsalt'                       => get_crumb('loginsalt'),
		);

	expose_var_to_js( 'display_login_js_handler_config', json_encode( $login_handler_config ) );
}


/**
 * Display lost password form
 *
 * @param string Login value
 * @param array login form hidden params
 * @param array Params
 */
function display_lostpassword_form( $login, $hidden_params, $params = array() )
{
	global $dummy_fields, $redirect_to, $Session;

	$params = array_merge( array(
			'form_before'     => '',
			'form_after'      => '',
			'form_action'     => get_htsrv_url( 'login' ).'login.php',
			'form_name'       => 'lostpass_form',
			'form_class'      => 'fform',
			'form_template'   => NULL,
			'inskin'          => true,
			'inskin_urls'     => true,
			'abort_link_text' => '',
		), $params );

	if( param( 'field_error', 'integer', 0 ) )
	{ // Mark login field as error because it was on page before redirection
		param_error( $dummy_fields['login'], '', '' );
	}

	$login_url = get_login_url( get_param( 'source' ), $redirect_to );

	$form_links = array();
	if( ! empty( $params['abort_link_text'] ) )
	{ // A link to "close" the window
		$form_links[] = '<a href="'.$login_url.'">'.$params['abort_link_text'].'</a>';
	}

	$form_links = count( $form_links ) ? '<span class="pull-right">'.implode( ' ', $form_links ).'</span>' : '';
	echo str_replace( '$form_links$', $form_links, $params['form_before'] );

	$Form = new Form( $params['form_action'], $params['form_name'], 'post', 'fieldset' );

	if( ! empty( $params['form_template'] ) )
	{ // Switch layout to template from array
		$params['form_template']['formstart'] = str_replace( '$form_links$', $form_links, $params['form_template']['formstart'] );

		$Form->switch_template_parts( $params['form_template'] );
	}

	$Form->begin_form( $params['form_class'] );

	// Display hidden fields
	$Form->add_crumb( 'lostpassform' );
	$Form->hidden( 'action', 'resetpassword' );
	foreach( $hidden_params as $key => $value )
	{
		$Form->hidden( $key, $value );
	}

	$Form->begin_fieldset();

	if( $params['inskin'] )
	{
		$Form->text( $dummy_fields[ 'login' ], $login, 30, /* TRANS: noun */ T_('Login'), '', 255, 'input_text' );
	}
	else
	{
		$Form->text_input( $dummy_fields[ 'login' ], $login, 30, '', '', array( 'maxlength' => 255, 'placeholder' => T_('Username (or email address)'), 'input_required' => 'required' ) );
	}

	$Form->buttons_input( array( array( /* TRANS: Text for submit button to request an activation link by email */ 'value' => T_('Send me a recovery email!'), 'class' => 'btn-primary btn-lg' ) ) );

	echo '<b>'.T_('How to reset your password:').'</b>';
	echo '<ol>';
	echo '<li>'.T_('Please enter your login (or email address) above.').'</li>';
	echo '<li>'.T_('An email will be sent to your registered email address immediately.').'</li>';
	echo '<li>'.T_('As soon as you receive the email, click on the link therein to reset your password.').'</li>';
	echo '<li>'.T_('Your browser will open a page where you can set a new password.').'</li>';
	echo '</ol>';
	echo '<p class="red"><strong>'.T_('Important: for security reasons, you must do steps 1 and 4 on the same computer and same web browser. Do not close your browser in between.').'</strong></p>';

	$login_link = '<a href="'.$login_url.'" class="floatleft">'.'&laquo; '.T_('Back to login form').'</a>';

	if( $params['inskin'] )
	{
		echo '<div class="login_actions" style="text-align:right; margin: 1em 0 1ex"><strong>';
		echo $login_link;
		echo '</strong></div>';
	}

	$Form->end_fieldset();

	$Form->end_form();

	echo $params['form_after'];

	if( ! $params['inskin'] )
	{
		echo '<div class="evo_form__login_links">';
		echo $login_link;
		echo '<div class="clear"></div>';
		echo '</div>';
	}
}


/**
 * Display user activate info form content
 *
 * @param Object activateinfo Form
 */
function display_activateinfo( $params )
{
	global $current_User, $Settings, $UserSettings, $Plugins;
	global $rsc_path, $rsc_url, $dummy_fields;

	if( !is_logged_in() )
	{ // if this happens, it means the code is not correct somewhere before this
		debug_die( "You must log in to see this page." );
	}

	$params = array_merge( array(
			'use_form_wrapper' => true,
			'form_before'      => '',
			'form_after'       => '',
			'form_action'      => get_htsrv_url( 'login' ).'login.php',
			'form_name'        => 'form_validatemail',
			'form_class'       => 'fform',
			'form_layout'      => 'fieldset',
			'form_template'    => NULL,
			'form_title'       => '',
			'inskin'           => false,
		), $params );

	// init force request new email address param
	$force_request = param( 'force_request', 'boolean', false );

	// get last activation email timestamp from User Settings
	$last_activation_email_date = $UserSettings->get( 'last_activation_email', $current_User->ID );

	if( $force_request || empty( $last_activation_email_date ) )
	{ // notification email was not sent yet, or user needs another one ( forced request )
		echo $params['use_form_wrapper'] ? $params['form_before'] : '';

		$Form = new Form( $params[ 'form_action' ], $params[ 'form_name' ], 'post', $params[ 'form_layout' ] );

		if( ! empty( $params['form_template'] ) )
		{ // Switch layout to template from array
			$Form->switch_template_parts( $params['form_template'] );
		}

		$Form->begin_form( $params[ 'form_class' ] );

		$Form->add_crumb( 'validateform' );
		$Form->hidden( 'action', 'req_activate_email');
		$Form->hidden( 'redirect_to', $params[ 'redirect_to' ] );
		if( $params[ 'inskin' ] )
		{
			$Form->hidden( 'inskin', $params[ 'inskin' ] );
			$Form->hidden( 'blog', $params[ 'blog' ] );
		}
		else
		{ // Form title in basic form
			echo '<h4>'.$params['form_title'].'</h4>';
		}
		$Form->hidden( 'req_activate_email_submit', 1 ); // to know if the form has been submitted

		$Form->begin_fieldset();

		echo '<ol>';
		echo '<li>'.T_('Please confirm your email address below:').'</li>';
		echo '</ol>';

		// set email text input content only if this is not a forced request. This way the user may have bigger chance to write a correct email address.
		$user_email = ( $force_request ? '' : $current_User->email );
		$Form->email_input( $dummy_fields[ 'email' ], $user_email, 42, T_('Your email'), array( 'maxlength' => 255, 'class' => 'input_text', 'required' => true, 'input_required' => 'required' ) );
		$Form->end_fieldset();

		// Submit button:
		$submit_button = array( array( 'name'=>'submit', 'value'=>T_('Send me a new activation email now!'), 'class'=>'btn-primary btn-lg' ) );

		$Form->buttons_input($submit_button);

		if( !$params[ 'inskin' ] )
		{
			$Plugins->trigger_event( 'DisplayValidateAccountFormFieldset', array( 'Form' => & $Form ) );
		}

		$Form->end_form();

		echo $params['use_form_wrapper'] ? $params['form_after'] : '';

		return;
	}

	// get notification email from general Settings
	$notification_email = $Settings->get( 'notification_sender_email' );
	// convert date to timestamp
	$last_activation_email_ts = mysql2timestamp( $last_activation_email_date );
	// get difference between local time and server time
	$time_difference = $Settings->get('time_difference');
	// get last activation email local date and time
	$last_email_date = date( locale_datefmt(), $last_activation_email_ts + $time_difference );
	$last_email_time = date( locale_shorttimefmt(), $last_activation_email_ts + $time_difference );
	$user_email = $current_User->email;

	echo $params['form_before'];

	if( ! $params['inskin'] )
	{
		echo '<div class="'.$params['form_class'].'">';
	}

	echo '<ol start="1" class="expanded">';
	$instruction =  sprintf( T_('Open your email account for %s and find a message we sent you on %s at %s with the following title:'), $user_email, $last_email_date, $last_email_time );
	echo '<li>'.$instruction.'<br /><b>'.sprintf( T_('Activate your account: %s'), $current_User->login ).'</b>';
	$request_validation_url = 'href="'.regenerate_url( '', 'force_request=1&validate_required=true&redirect_to='.rawurlencode( $params[ 'redirect_to' ] ) ).'"';
	echo '<p>'.sprintf( T_('NOTE: If you don\'t find it, check your "Junk", "Spam" or "Unsolicited email" folders. If you really can\'t find it, <a %s>request a new activation email</a>.'), $request_validation_url ).'</p></li>';
	echo '<li>'.sprintf( T_('Add us (%s) to your contacts to make sure you receive future email notifications, especially when someone sends you a private message.'), '<b><span class="nowrap">'.$notification_email.'</span></b>').'</li>';
	echo '<li><b class="red">'.T_('Click on the activation link in the email.').'</b>';
	echo '<p>'.T_('If this does not work, please copy/paste that link into the address bar of your browser.').'</p>';
	echo '<p>'.sprintf( T_('If you need assistance, please send an email to %s'), '<b><a href="mailto:"'.$notification_email.'"><span class="nowrap">'.$notification_email.'</span></a></b>' ).'</p></li>';
	echo '</ol>';

	if( ( strpos( $user_email, '@hotmail.' ) || strpos( $user_email, '@live.' ) || strpos( $user_email, '@msn.' ) )
		&& file_exists( $rsc_path.'img/login_help/hotmail-validation.png' ) )
	{ // The user is on hotmail and we have a help screen to show him: (needs to be localized and include correct site name)
		echo '<div class="center" style="margin: 2em auto"><img src="'.$rsc_url.'img/login_help/hotmail-validation.png" /></div>';
	}
	elseif( ( strpos( $user_email, '@gmail.com' ) || strpos( $user_email, '@googlemail.com' ) )
		&& file_exists( $rsc_path.'img/login_help/gmail-validation.png' ) )
	{ // The user is on hotmail and we have a help screen to show him: (needs to be localized and include correct site name)
		echo '<div class="center" style="margin: 2em auto"><img src="'.$rsc_url.'img/login_help/gmail-validation.png" /></div>';
	}

	if( ! $params['inskin'] )
	{
		echo '</div>';
	}

	echo $params['form_after'];

	if( $current_User->grp_ID == 1 )
	{ // allow admin users to validate themselves by a single click:
		global $Session, $redirect_to;

		if( empty( $redirect_to ) )
		{ // Set where to redirect
			$redirect_to = regenerate_url();
		}

		echo $params['use_form_wrapper'] ? $params['form_before'] : '';

		$Form = new Form( get_htsrv_url( 'login' ).'login.php', 'form_validatemail', 'post', 'fieldset' );

		if( ! empty( $params['form_template'] ) )
		{ // Switch layout to template from array
			$Form->switch_template_parts( $params['form_template'] );
		}

		$Form->begin_form( 'evo_form__login' );

		$Form->add_crumb( 'validateform' );
		$Form->hidden( 'action', 'activateacc_sec' );
		$Form->hidden( 'redirect_to', url_rel_to_same_host( $redirect_to, get_htsrv_url( 'login' ) ) );
		$Form->hidden( 'reqID', 1 );
		$Form->hidden( 'sessID', $Session->ID );

		echo '<p>'.sprintf( T_('Since you are an admin user, you can activate your account (%s) by a single click.' ), $current_User->email ).'</p>';
		// TODO: the form submit value is too wide (in Konqueror and most probably in IE!)
		$Form->end_form( array( array(
				'name'  => 'form_validatemail_admin_submit',
				'value' => T_('Activate my account!'),
				'class' => 'ActionButton btn btn-primary'
			) ) ); // display hidden fields etc

		echo $params['use_form_wrapper'] ? $params['form_after'] : '';
	}

	echo '<div class="evo_form__login_links floatright">';
	user_logout_link();
	echo '</div>';
}


/*
 * Display javascript password strength indicator bar
 *
 * @param array Params
 */
function display_password_indicator( $params = array() )
{
	global $Settings, $Collection, $Blog, $rsc_url, $disp, $dummy_fields;

	$password_indicator_config = array_merge( array(
			'pass1_id'    => $dummy_fields[ 'pass1' ],
			'pass2_id'    => $dummy_fields[ 'pass2' ],
			'login_id'    => $dummy_fields[ 'login' ],
			'email_id'    => $dummy_fields[ 'email' ],
			'field_width' => NULL, // Use current field width
			'disp_status' => true,
			'disp_time'   => false,
			'blacklist'   => array( 'b2evo', 'b2evolution' ), // Identify the password as "weak" if it includes any of these words
			'rsc_url'     => force_https_url( $rsc_url, 'login'),
			'msg_status_very_weak' => format_to_output( T_('Very weak'), 'htmlattr' ),
			'msg_status_weak'      => format_to_output( T_('Weak'), 'htmlattr' ),
			'msg_status_soso'      => format_to_output( T_('So-so'), 'htmlattr' ),
			'msg_status_good'      => format_to_output( T_('Good'), 'htmlattr' ),
			'msg_status_great'     => format_to_output( T_('Great'), 'htmlattr' ),
			'msg_est_crack_time'   => T_('Estimated crack time'),
			'msg_illegal_char'     => sprintf( TS_('Password cannot contain the following characters: %s'), '<code><</code> <code>></code> <code>&</code>' ),
			'msg_min_pwd_len'      => sprintf( TS_('The minimum password length is %d characters.'), $Settings->get( 'user_minpwdlen' ) ),
			'msg_pwd_not_matching' => TS_('The second password is different from the first.'),
			'min_pwd_len'          => (int) $Settings->get( 'user_minpwdlen' ),
			'error_icon'           => get_icon( 'xross' ),
		), $params );

	expose_var_to_js( 'evo_init_password_indicator_config', evo_json_encode( $password_indicator_config ) );
}


/*
 * Display javascript code to edit password
 *
 * @param array Params
 */
function display_password_js_edit()
{
	global $Settings;

	$password_edit_config = array(
			'user_minpwdlen' => intval( $Settings->get('user_minpwdlen') ),
			'msg_pwd_trim_warning' => T_('The leading and trailing spaces will be trimmed.'),
		);

	expose_var_to_js( 'evo_init_password_edit_config', evo_json_encode( $password_edit_config ) );
}


/*
 * Display javascript login validator
 *
 * @param array Params
 */
function display_login_validator( $params = array() )
{
	global $rsc_url, $dummy_fields;

	$login_validator_config = array(
			'login_id'             => $dummy_fields[ 'login' ],
			'rsc_url'              => $rsc_url,
			'login_htsrv_url'      => get_htsrv_url( 'login' ),
			'login_icon_load'      => '<img src="'.$rsc_url.'img/ajax-loader.gif" alt="'.T_('Loading...').'" title="'.T_('Loading...').'" style="margin:2px 0 0 5px" align="top" />',
			'login_icon_available' => get_icon( 'allowback', 'imgtag', array( 'title' => T_('This username is available.') ) ),
			'login_icon_exists'    => get_icon( 'xross', 'imgtag', array( 'title' => T_('This username is already in use. Please choose another one.') ) ),
			'login_icon_error'     => get_icon( 'xross', 'imgtag', array( 'title' => '$error_msg$' ) ),
			'login_text_empty'     => T_('Choose a username'),
			'login_text_available' => T_('This username is available.'),
			'login_text_exists'    => T_('This username is already in use. Please choose another one.'),
		);

	expose_var_to_js( 'evo_init_login_validator_config', evo_json_encode( $login_validator_config ) );
}


/*
 * Display javascript to quick edit field by AJAX
 * Used to edit fields such as 'order' by one click on value in table list
 *
 * @param array Params
 */
function init_field_editor_js( $params = array() )
{
	// Make sure we are not missing any param:
	$params = array_merge( array(
			'field_prefix' => 'order-',
			'action_url'   => '',
			'question'     => TS_("Do you want discard your changes for this order field?"),
			'relative_to'  => 'rsc_url',
		), $params );

	require_js_defer( '#jquery#', $params['relative_to'] ); // dependency

	add_js_headline( 'jQuery( document ).on( "click", "[id^='.$params['field_prefix'].']", function()
{
	if( jQuery( this ).find( "input" ).length > 0 )
	{ // This order field is already editing now
		return false;
	}

	// Get current value and wrapper if value is stored in <span>:
	var value = jQuery( this ).html();
	var wrapper = false;
	if( jQuery( this ).find( "span" ).length > 0 )
	{
		wrapper = jQuery( this ).find( "span" );
		value = wrapper.html();
	}

	// Create <input> to edit order field
	var input = document.createElement( "input" )
	var $input = jQuery( input );
	$input.val( value );
	$input.css( {
		width: jQuery( this ).width(),
		height: jQuery( this ).height(),
		"line-height": jQuery( this ).height() + "px",
		padding: "0",
		"text-align": "center",
		"vertical-align": "bottom",
	} );

	// Save current value
	jQuery( this ).attr( "rel", value );

	// Replace static value with <input>:
	if( wrapper === false )
	{
		jQuery( this ).html( "" );
	}
	else
	{
		wrapper.hide();
	}
	jQuery( this ).append( $input );
	$input.focus();

	// Bind events for <input>
	$input.bind( "keydown", function( e )
	{
		var key = e.keyCode;
		var parent_obj = jQuery( this ).parent();
		if( key == 27 )
		{ // "Esc" key
			if( wrapper === false )
			{
				parent_obj.html( parent_obj.attr( "rel" ) );
			}
			else
			{
				parent_obj.find( "input" ).remove();
				wrapper.show();
			}
		}
		else if( key == 13 )
		{ // "Enter" key
			results_ajax_load( jQuery( this ), "'.$params['action_url'].'" + parent_obj.attr( "id" ) + "&new_value=" + jQuery( this ).val() );
		}
	} );

	$input.bind( "blur", function()
	{
		var revert_changes = false;

		var parent_obj = jQuery( this ).parent();
		if( parent_obj.attr( "rel" ) != jQuery( this ).val() )
		{ // Value was changed, ask about saving
			if( confirm( "'.$params['question'].'" ) )
			{
				revert_changes = true;
			}
		}
		else
		{
			revert_changes = true;
		}

		if( revert_changes )
		{ // Revert the changed value
			if( wrapper === false )
			{
				parent_obj.html( parent_obj.attr( "rel" ) );
			}
			else
			{
				parent_obj.find( "input" ).remove();
				wrapper.show();
			}
		}
	} );

	return false;
} );' );
}


/**
 * Registers headlines for initialization of functions to autocomplete usernames in textarea
 */
function init_autocomplete_usernames_js( $relative_to = 'rsc_url' )
{
	global $Collection, $Blog;

	if( ! empty( $Blog ) )
	{	// Set global blog ID for textcomplete(Used to sort users by collection members and assignees):
		add_js_headline( 'var blog = '.$Blog->ID );
	}
	require_js_defer( '#jquery#', $relative_to );
	require_js_defer( 'build/textcomplete.bmin.js', $relative_to );
}


/**
 * Get JS code to prevent event of the key "Enter" for selected elements,
 * Used to stop form submitting by enter on some input fields
 *
 * @param string Selection for jQuery selector
 */
function get_prevent_key_enter_js( $jquery_selection )
{
	if( empty( $jquery_selection ) )
	{ // jQuery selection must be filled
		return '';
	}

	return 'jQuery( "'.$jquery_selection.'" ).keypress( function( e ) { if( e.keyCode == 13 ) return false; } );';
}


/**
 * Initialize CSS and JS to use font-awesome icons
 *
 * @param string Icons type:
 *               - 'fontawesome' - Use only font-awesome icons
 *               - 'fontawesome-glyphicons' - Use font-awesome icons as a priority over the glyphicons
 * @param boolean|string 'relative' or true (relative to <base>) or 'rsc_url' (relative to $rsc_url) or 'blog' (relative to current blog URL -- may be subdomain or custom domain)
 * @param boolean TRUE - to require css file, FALSE - is used when css file is already loaded inside superbundle file
 */
function init_fontawesome_icons( $icons_type = 'fontawesome', $relative_to = 'rsc_url', $require_files = true )
{
	global $b2evo_icons_type;

	// Use font-awesome icons, @see get_icon()
	$b2evo_icons_type = $icons_type;

	if( $require_files )
	{	// Load main CSS file of font-awesome icons
		require_css( '#fontawesome#', $relative_to );
	}
}


/**
 * Get rating stars template
 *
 * @param float Rating value, e-g: 2 - display 2 active stars of default 5, 4.33 - display 4 active stars and 5 star is filled for 33%
 * @param integer Total number of stars
 * @param array Additional parameters
 * @return string HTML of stars
 */
function get_star_rating( $value, $stars_num = 5, $params = array() )
{
	global $b2evo_icons_type;

	if( isset( $b2evo_icons_type ) && strpos( $b2evo_icons_type, 'fontawesome' ) !== false )
	{	// Use font-awesome stars if it is allowed for current skin:
		$icon_type = 'fa';
		$default_params = array(
			'stars_before'       => '<span class="evo_stars">',
			'stars_star_full'    => '<i class="fa fa-star"></i>',
			'stars_star_percent' => '<i class="fa fa-star evo_star_percent"><i class="fa fa-star" style="width:$percent$"></i></i>', // $percent$ is replaced with values like 10%, 67%
			'stars_star_empty'   => '<i class="fa fa-star evo_star_empty"></i>',
			'stars_after'        => '</span>',
		);
	}
	else
	{	// Use image stars for v5 skins:
		$icon_type = 'img';
		$default_params = array(
			'stars_before'       => '<span class="evo_stars_img" style="width:$stars_width$px">',
			'stars_star_full'    => '<i>*</i>',
			'stars_star_percent' => '<i class="evo_stars_img_empty"><i style="width:$percent$">%</i></i>', // $percent$ is replaced with values like 10%, 67%
			'stars_star_empty'   => '<i class="evo_stars_img_empty">-</i>',
			'stars_after'        => '</span>',
		);
	}

	$params = array_merge( $default_params, $params );

	if( ! is_numeric( $stars_num ) )
	{	// Fix for old function where second param was a string:
		$stars_num = 5;
	}

	$stars_num = intval( $stars_num );

	if( $stars_num < 1 )
	{	// Nothing to display:
		return '';
	}

	if( $icon_type == 'fa' )
	{
		$stars_template = $params['stars_before'];
	}
	else
	{	// Image icons must have a specific width depending on number of stars:
		// (16px is width of one image star icon)
		$stars_template = str_replace( '$stars_width$', $stars_num * 16, $params['stars_before'] );
	}

	$full_stars_max = floor( $value );
	$percents = round( ( $value - $full_stars_max ) * 100 );
	for( $s = 1; $s <= $stars_num; $s++ )
	{
		if( $s == $full_stars_max + 1 && $percents > 0 )
		{	// Percent star:
			$stars_template .= str_replace( '$percent$', $percents.'%', $params['stars_star_percent'] );
		}
		elseif( $s > $full_stars_max )
		{	// Empty star:
			$stars_template .= $params['stars_star_empty'];
		}
		else
		{	// Full star:
			$stars_template .= $params['stars_star_full'];
		}
	}

	$stars_template .= $params['stars_after'];

	return $stars_template;
}


/**
 * Registers headlines for initialization of file multi uploader
 *
 * @param boolean|string 'relative' or true (relative to <base>) or 'rsc_url' (relative to $rsc_url) or 'blog' (relative to current blog URL -- may be subdomain or custom domain)
 * @param boolean TRUE to make the links table sortable
 */
function init_fileuploader_js( $relative_to = 'rsc_url', $load_sortable_js = true )
{
	require_js_defer( '#jquery#', $relative_to, true );
	// Used to make uploader area resizable:
	require_js_defer( '#jqueryUI#', $relative_to, true );

	if( $load_sortable_js )
	{	// Load JS file uploader with sortable feature for links/attachments:
		require_js_defer( 'build/evo_fileuploader_sortable.bmin.js', $relative_to, true );
	}
	else
	{	// Load JS file uploader:
		require_js_defer( 'build/evo_fileuploader.bmin.js', $relative_to, true );
	}

	// Styles for file uploader:
	require_css( 'fine-uploader.css', $relative_to, NULL, NULL, '#', true );
}


/**
 * Get a label for PRO version
 *
 * @return string
 */
function get_pro_label()
{
	return '<span class="label label-sm label-primary">PRO</span>';
}


/**
 * Resolve auto content mode depending on current disp detail
 *
 * @param string Content mode
 * @param object Collection
 * @return string Content mode
 */
function resolve_auto_content_mode( $content_mode, $setting_Blog = NULL )
{
	global $disp_detail;

	if( $content_mode != 'auto' )
	{	// Use this function only for auto content mode:
		return $content_mode;
	}

	if( $setting_Blog === NULL )
	{	// Use current Collection:
		global $Blog;
		$setting_Blog = $Blog;
	}

	if( empty( $setting_Blog ) )
	{	// Collection must be defined on call this function:
		debug_die( 'Collection is not initialized to resolve auto content mode!' );
	}

	switch( $disp_detail )
	{
		case 'posts-cat':
		case 'posts-topcat-intro':
		case 'posts-topcat-nointro':
		case 'posts-subcat-intro':
		case 'posts-subcat-nointro':
			return $setting_Blog->get_setting('chapter_content');

		case 'posts-tag':
			return $setting_Blog->get_setting('tag_content');

		case 'posts-date':
			return $setting_Blog->get_setting('archive_content');

		case 'single':
		case 'page':
			return 'full';

		case 'posts-default':  // home page 1
		case 'posts-next':     // next page 2, 3, etc
		case 'posts-next-intro':   // next page with intro
		case 'posts-next-nointro': // next page without intro
			return $setting_Blog->get_setting('main_content');

		default: // posts-filtered, search, flagged and etc.
			return $setting_Blog->get_setting('filtered_content');
	}
}
?>
