Seditio Source
Root |
./othercms/b2evolution_7.2.3/inc/_core/_template.funcs.php
<?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_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');
    }
}
?>