Seditio Source
Root |
./othercms/b2evolution_7.2.3/inc/_core/_misc.funcs.php
<?php
/**
 * This file implements general purpose functions.
 *
 * 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}.
 * Parts of this file are copyright (c)2005-2006 by PROGIDISTRI - {@link http://progidistri.com/}.
 *
 * @package evocore
 */
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );


/**
 * Dependencies
 */
load_funcs('antispam/model/_antispam.funcs.php');
load_funcs('tools/model/_email.funcs.php');
load_funcs('sessions/model/_cookie.funcs.php');

// @todo sam2kb> Move core functions get_admin_skins, get_filenames, cleardir_r, rmdir_r and some other
// to a separate file, and split files_Module from _core_Module
load_funcs('files/model/_file.funcs.php');

// Load utf8 support functions
load_funcs( '_ext/_portable_utf8.php' );


/**
 * Call a method for all modules in a row
 *
 * @param string the name of the method which should be called
 * @param array params
 * @return array[module_name][return value], or NULL if the method doesn't have any return value
 */
function modules_call_method( $method_name, $params = NULL )
{
    global
$modules;

   
$result = NULL;

    foreach(
$modules as $module )
    {
       
$Module = & $GLOBALS[$module.'_Module'];
        if(
$params == NULL )
        {
           
$ret = $Module->{$method_name}();
        }
        else
        {
           
$ret = $Module->{$method_name}( $params );
        }
        if( isset(
$ret ) )
        {
           
$result[$module] = $ret;
        }
    }

    return
$result;
}


/**
 * Call a method for all modules in a row and update params by reference
 *
 * @param string the name of the method which should be called
 * @param array params
 * @return array[module_name][return value], or NULL if the method doesn't have any return value
 */
function modules_call_method_reference_params( $method_name, & $params )
{
    global
$modules;

   
$result = NULL;

    foreach(
$modules as $module )
    {
       
$Module = & $GLOBALS[$module.'_Module'];
       
$ret = $Module->{$method_name}( $params );
        if( isset(
$ret ) )
        {
           
$result[$module] = $ret;
        }
    }

    return
$result;
}


/**
 * Loads the b2evo database scheme.
 *
 * This gets updated through {@link db_delta()} which generates the queries needed to get
 * to this scheme.
 *
 * @param boolean set true to load installed plugins table as well, leave it on false otherwise
 *        - currently used only on table normalization
 *
 * Please see {@link db_delta()} for things to take care of.
 */
function load_db_schema( $inlcude_plugins = false )
{
    global
$schema_queries;
    global
$modules, $inc_path;
    global
$db_storage_charset, $DB;

    if( empty(
$db_storage_charset ) )
    {
// If no specific charset has been requested for datstorage, use the one of the current connection (optimize for speed - no conversions)
       
$db_storage_charset = $DB->get_connection_charset();
    }

   
// Load modules:
   
foreach( $modules as $module )
    {
        echo
get_install_format_text_and_log( 'Loading module: <code>'.$module.'/model/_'.$module.'.install.php</code><br />', 'br' );
        require_once
$inc_path.$module.'/model/_'.$module.'.install.php';
    }

    if(
$inlcude_plugins )
    {
// Load all plugins table into the schema queries
       
global $Plugins;

        if( empty(
$Plugins ) )
        {
           
load_class( 'plugins/model/_plugins.class.php', 'Plugins' );
           
$Plugins = new Plugins();
        }

       
$admin_Plugins = & get_Plugins_admin();
       
$admin_Plugins->restart();
        while(
$loop_Plugin = & $admin_Plugins->get_next() )
        {
// loop through all installed plugins
           
$create_table_queries = $loop_Plugin->GetDbLayout();
            foreach(
$create_table_queries as $create_table_query )
            {
                if( !
preg_match( '|^\s*CREATE TABLE\s+(IF NOT EXISTS\s+)?([^\s(]+).*$|is', $create_table_query, $match) )
                {
// Could not parse the CREATE TABLE command
                   
continue;
                }
               
$schema_queries[$match[2]] = array( 'Creating table for plugin', $create_table_query );
               
$DB->dbaliases[] = '#\b'.$match[2].'\b#';
               
$DB->dbreplaces[] = $match[2];
            }
        }
    }
}


/**
 * @deprecated kept only for plugin backward compatibility (core is being modified to call getters directly)
 * To be removed, maybe in b2evo v5.
 *
 * @return DataObjectCache
 */
function & get_Cache( $objectName )
{
    global
$Plugins;
    global $
$objectName;

    if( isset( $
$objectName ) )
    {    
// Cache already exists:
       
return $$objectName;
    }

   
$func_name = 'get_'.$objectName;

    if(
function_exists($func_name) )
    {
        return
$func_name();
    }
    else
    {
       
debug_die( 'getCache(): Unknown Cache type get function:'.$func_name.'()' );
    }
}


/**
 * Load functions file
 */
function load_funcs( $funcs_path )
{
    global
$inc_path;
    require_once
$inc_path.$funcs_path;
}


/**
 * Shutdown function: save HIT and update session!
 *
 * This is registered in _main.inc.php with register_shutdown_function()
 * This is called by PHP at the end of the script.
 *
 * NOTE: before PHP 4.1 nothing can be echoed here any more, but the minimum PHP requirement for b2evo is PHP 4.3
 */
function shutdown()
{
   
/**
     * @var Hit
     */
   
global $Hit;

   
/**
     * @var Session
     */
   
global $Session;

    global
$Settings, $Debuglog, $DB, $Timer;

   
// Try forking a background process and let the parent return as fast as possbile.
   
if( is_callable('pcntl_fork') && function_exists('posix_kill') && defined('STDIN') )
    {
        if(
$pid = pcntl_fork() )
            return;
// Parent

       
function shutdown_kill()
        {
           
posix_kill(posix_getpid(), SIGHUP);
        }

        if (
ob_get_level() )
        {    
// Discard the output buffer and close
           
ob_end_clean();
        }

       
fclose(STDIN);  // Close all of the standard
       
fclose(STDOUT); // file descriptors as we
       
fclose(STDERR); // are running as a daemon.

       
register_shutdown_function('shutdown_kill');

        if(
posix_setsid() < 0 )
            return;

        if(
$pid = pcntl_fork() )
            return;    
// Parent

        // Now running as a daemon. This process will even survive
        // an apachectl stop.
   
}

   
$Timer->resume('shutdown');

   
// echo '*** SHUTDOWN FUNC KICKING IN ***';

    // fp> do we need special processing if we are in CLI mode?  probably earlier actually
    // if( ! $is_cli )

    // Note: it might be useful at some point to do special processing if the script has been aborted or has timed out
    // connection_aborted()
    // connection_status()

    // Save the current HIT, but set delayed since the hit ID will not be required here:
   
$Hit->log( true );

   
// Update the SESSION:
   
$Session->dbsave();

   
// Get updates here instead of slowing down normal display of the dashboard
   
load_funcs( 'dashboard/model/_dashboard.funcs.php' );
   
b2evonet_get_updates();

   
// Auto pruning of old HITS, old SESSIONS and potentially MORE analytics data:
   
if( $Settings->get( 'auto_prune_stats_mode' ) == 'page' )
    {
// Autopruning is requested
       
load_class( 'sessions/model/_hitlist.class.php', 'Hitlist' );
       
Hitlist::dbprune(); // will prune once per day, according to Settings
   
}

   
// Calling debug_info() here will produce complete data but it will be after </html> hence invalid.
    // Then again, it's for debug only, so it shouldn't matter that much.
   
debug_info();

   
// Update the SESSION again, at the very end:
    // (e.g. "Debuglogs" may have been removed in debug_info())
   
$Session->dbsave();

    while(
$DB->transaction_nesting_level )
    {    
// Rollback all transactions which could not finished correctly by some unknown error,
        // Used to avoid errors like "Lock wait timeout exceeded; try restarting transaction":
       
$DB->rollback();
    }

   
$Timer->pause('shutdown');
}


/***** Formatting functions *****/

/**
 * Format a string/content for being output
 *
 * @author fplanque
 * @todo htmlspecialchars() takes a charset argument, which we could provide ($evo_charset?)
 * @param string raw text
 * @param string format, can be one of the following
 * - raw: do nothing
 * - htmlbody: display in HTML page body: allow full HTML
 * - entityencoded: Special mode for RSS 0.92: allow full HTML but escape it
 * - htmlhead: strips out HTML (mainly for use in Title)
 * - htmlattr: use as an attribute: escapes quotes, strip tags
 * - formvalue: use as a form value: escapes quotes and < > but leaves code alone
 * - text: use as plain-text, e.g. for ascii-mails
 * - xml: use in an XML file: strip HTML tags
 * - xmlattr: use as an attribute: strips tags and escapes quotes
 * @return string formatted text
 */
function format_to_output( $content, $format = 'htmlbody' )
{
    global
$Plugins, $evo_charset;

    switch(
$format )
    {
        case
'raw':
           
// do nothing!
           
break;

        case
'htmlbody':
           
// display in HTML page body: allow full HTML
           
$content = convert_chars($content, 'html');
            break;

        case
'urlencoded':
           
// Encode string to be passed as part of an URL
           
$content = rawurlencode( $content );
            break;

        case
'entityencoded':
           
// Special mode for RSS 0.92: apply renders and allow full HTML but escape it
           
$content = convert_chars($content, 'html');
           
$content = htmlspecialchars( $content, ENT_QUOTES, $evo_charset );
            break;

        case
'htmlfeed':
           
// For use in RSS <content:encoded><![CDATA[ ... ]]></content:encoded>
            // allow full HTML + absolute URLs...
           
$content = make_rel_links_abs($content);
           
$content = convert_chars($content, 'html');
           
$content = str_replace(']]>', ']]&gt;', $content); // encode CDATA closing tag to prevent injection/breaking of the <![CDATA[ ... ]]>
           
break;

        case
'htmlhead':
           
// Strips out HTML (mainly for use in Title)
           
$content = strip_tags($content);
           
$content = convert_chars($content, 'html');
            break;

        case
'htmlattr':
           
// use as an attribute: strips tags and escapes quotes
            // TODO: dh> why not just htmlspecialchars?fp> because an attribute can never contain a tag? dh> well, "onclick='return 1<2;'" would get stripped, too. I'm just saying: why mess with it, when we can just use htmlspecialchars.. fp>ok
           
$content = strip_tags($content);
           
$content = convert_chars($content, 'html');
           
$content = str_replace( array('"', "'"), array('&quot;', '&#039;'), $content );
            break;

        case
'htmlspecialchars':
        case
'formvalue':
           
// Replace special chars to &amp;, &quot;, &#039;|&apos;, &lt; and &gt; :
            // Handles & " ' < > to &amp; &quot; &apos; &lt; &gt;
           
$content = htmlspecialchars( $content, ENT_QUOTES | ENT_HTML5, $evo_charset );
            break;

        case
'xml':
           
// use in an XML file: strip HTML tags
           
$content = strip_tags($content);
           
$content = convert_chars($content, 'xml');
            break;

        case
'xmlattr':
           
// use as an attribute: strips tags and escapes quotes
           
$content = strip_tags($content);
           
$content = convert_chars($content, 'xml');
           
$content = str_replace( array('"', "'"), array('&quot;', '&#039;'), $content );
            break;

        case
'text':
           
// use as plain-text, e.g. for ascii-mails
           
$content = strip_tags( $content );
           
$trans_tbl = get_html_translation_table( HTML_ENTITIES );
           
$trans_tbl = array_flip( $trans_tbl );
           
$content = strtr( $content, $trans_tbl );
           
$content = preg_replace( '/[ \t]+/', ' ', $content);
           
$content = trim($content);
            break;

        case
'syslog':
           
// Replace special chars to &amp;, &quot;, &#039;|&apos;, &lt; and &gt; :
            // Handles & " ' < > to &amp; &quot; &apos; &lt; &gt;
           
$content = htmlspecialchars( $content, ENT_QUOTES | ENT_HTML5, $evo_charset );
           
$content = preg_replace( "/\[\[(.+?)]]/is", "<code>$1</code>", $content ); // Replaces [[...]] into <code>...</code>
           
break;

        default:
           
debug_die( 'Output format ['.$format.'] not supported.' );
    }

    return
$content;
}


/*
 * autobrize(-)
 */
function autobrize($content) {
   
$content = callback_on_non_matching_blocks( $content, '~<code>.+?</code>~is', 'autobrize_callback' );
    return
$content;
}

/**
 * Adds <br>'s to non code blocks
 *
 * @param string $content
 * @return string content with <br>'s added
 */
function autobrize_callback( $content )
{
   
$content = preg_replace("/<br>\n/", "\n", $content);
   
$content = preg_replace("/<br \/>\n/", "\n", $content);
   
$content = preg_replace("/(\015\012)|(\015)|(\012)/", "<br />\n", $content);
    return(
$content);
}

/*
 * unautobrize(-)
 */
function unautobrize($content)
{
   
$content = callback_on_non_matching_blocks( $content, '~<code>.+?</code>~is', 'unautobrize_callback' );
    return
$content;
}

/**
 * Removes <br>'s from non code blocks
 *
 * @param string $content
 * @return string content with <br>'s removed
 */
function unautobrize_callback( $content )
{
   
$content = preg_replace("/<br>\n/", "\n", $content);   //for PHP versions before 4.0.5
   
$content = preg_replace("/<br \/>\n/", "\n", $content);
    return(
$content);
}

/**
 * Add leading zeroes to a number when necessary.
 *
 * @param string The original number.
 * @param integer How many digits shall the number have?
 * @return string The padded number.
 */
function zeroise( $number, $threshold )
{
    return
str_pad( $number, $threshold, '0', STR_PAD_LEFT );
}


/**
 * Get a limited text-only excerpt
 *
 * @param string
 * @param int Maximum length
 * @return string
 */
function excerpt( $str, $maxlen = 254, $tail = '&hellip;' )
{
   
// Add spaces
   
$str = str_replace( array( '<p>', '<br', '</tr><tr', '</th><th', '</td><td' ), array( ' <p>', ' <br', '</tr> <tr', '</th> <th', '</td> <td' ), $str );

   
// Remove <code>
   
$str = preg_replace( '#<code>(.+)</code>#is', '', $str );

   
// Strip tags:
   
$str = strip_tags( $str );

   
// Remove spaces:
   
$str = preg_replace( '/[ \t]+/', ' ', $str);
   
$str = trim( $str );

   
// Ger rid of all new lines and Display the html tags as source text:
   
$str = trim( preg_replace( '#[\r\n\t\s]+#', ' ', $str ) );

   
$str = strmaxlen( $str, $maxlen, $tail, 'raw', true );

    return
$str;
}


/**
 * Get a limited text-only excerpt based on number of words
 *
 * @param string
 * @param integer Maximum length
 * @param array Params
 * @return string
 */
function excerpt_words( $str, $maxwords = 50, $params = array() )
{
   
// Add spaces
   
$str = str_replace( array( '<p>', '<br' ), array( ' <p>', ' <br' ), $str );

   
// Remove <code>
   
$str = preg_replace( '#<code>(.+)</code>#is', '', $str );

   
// Strip tags:
   
$str = strip_tags( $str );

   
// Remove spaces:
   
$str = preg_replace( '/[ \t]+/', ' ', $str);
   
$str = trim( $str );

   
// Ger rid of all new lines and Display the html tags as source text:
   
$str = trim( preg_replace( '#[\r\n\t\s]+#', ' ', $str ) );

   
$str = strmaxwords( $str, $maxwords, $params );

    return
$str;
}


/**
 * Crop string to maxlen with &hellip; (default tail) at the end if needed.
 *
 * If $format is not "raw", we make sure to not cut in the middle of an
 * HTML entity, so that strmaxlen('1&amp;2', 3, NULL, 'formvalue') will not
 * become/stay '1&amp;&hellip;'.
 *
 * @param string
 * @param int Maximum length
 * @param string Tail to use, when string gets cropped. Its length gets
 *               substracted from the total length (with HTML entities
 *               being decoded). Default is "&hellip;" (HTML entity)
 * @param string Format, see {@link format_to_output()}
 * @param boolean Crop at whitespace, if possible?
 *        (any word split at the end will get its head removed)
 * @return string
 */
function strmaxlen( $str, $maxlen = 50, $tail = NULL, $format = 'raw', $cut_at_whitespace = false  )
{
    if(
is_null($tail) )
    {
       
$tail = '&hellip;';
    }

   
$str = utf8_rtrim($str);

    if(
utf8_strlen( $str ) > $maxlen )
    {
       
// Replace all HTML entities by a single char. html_entity_decode for example
        // would not handle &hellip;.
       
$tail_for_length = preg_replace('~&\w+?;~', '.', $tail);
       
$tail_length = utf8_strlen( html_entity_decode($tail_for_length) );
       
$len = $maxlen-$tail_length;
        if(
$len < 1 )
        {
// special case; $tail length is >= $maxlen
           
$len = 0;
        }
       
$str_cropped = utf8_substr( $str, 0, $len );
        if(
$format != 'raw' )
        {
// if the format isn't raw we make sure that we do not cut in the middle of an HTML entity
           
$maxlen_entity = 7; # "&amp;" is 5, min 3!
           
$str_inspect = utf8_substr($str_cropped, 1-$maxlen_entity);
           
$pos_amp = utf8_strpos($str_inspect, '&');
            if(
$pos_amp !== false )
            {
// there's an ampersand at the end of the cropped string
               
$look_until = $pos_amp;
               
$str_cropped_len = utf8_strlen($str_cropped);
                if(
$str_cropped_len < $maxlen_entity )
                {
// we have to look at least for the length of an entity
                   
$look_until += $maxlen_entity-$str_cropped_len;
                }
                if(
strpos(utf8_substr($str, $len, $look_until), ';') !== false )
                {
                   
$str_cropped = utf8_substr( $str, 0, $len-utf8_strlen($str_inspect)+$pos_amp);
                }
            }
        }

        if(
$cut_at_whitespace )
        {
           
// Get the first character being cut off. Note: we can't use $str[index] in case of utf8 strings!
           
$first_cut_off_char = utf8_substr( $str, utf8_strlen( $str_cropped ), 1 );
            if( !
ctype_space( $first_cut_off_char ) )
            {
// first character being cut off is not whitespace
                // Get the chars as an array from the cropped string to be able to get chars by position
               
$str_cropped_chars = preg_split('//u',$str_cropped, -1, PREG_SPLIT_NO_EMPTY);
               
$i = utf8_strlen($str_cropped);
                while(
$i && isset( $str_cropped_chars[ --$i ] ) && ! ctype_space( $str_cropped_chars[ $i ] ) )
                {}
                if(
$i )
                {
                   
$str_cropped = utf8_substr($str_cropped, 0, $i);
                }
            }
        }

       
$str = format_to_output(utf8_rtrim($str_cropped), $format);
       
$str .= $tail;

        return
$str;
    }
    else
    {
        return
format_to_output($str, $format);
    }
}


/**
 * Crop string to maxwords preserving tags.
 *
 * @param string
 * @param int Maximum number words
 * @param mixed array Optional parameters
 * @return string
 */
function strmaxwords( $str, $maxwords = 50, $params = array() )
{
   
$params = array_merge( array(
           
'cutting_mark'    => '&hellip;',
           
'continued_link'  => '',
           
'continued_text'  => '&hellip;',
           
'continued_class' => '',
           
'always_continue' => false,
        ),
$params );

   
// STATE MACHINE:
   
$in_tag = false;
   
$have_seen_non_whitespace = false;
   
$end = strlen( $str );  // If we use utf8_strlen here(), we also need to access UTF chars below
   
for( $i = 0; $i < $end; $i++ )
    {
        switch(
$char = $str[$i] )        // This is NOT UTF-8
       
{
            case
'<' :    // start of a tag
               
$in_tag = true;
                break;

            case
'>' : // end of a tag
               
$in_tag = false;
                break;

            case
ctype_space($char): // This is a whitespace char...
               
if( ! $in_tag )
                {    
// it's a word gap:
                    // Eat any additional whitespace:
                   
while( isset($str[$i+1]) && ctype_space($str[$i+1]) )
                    {
                       
$i++;
                    }
                    if( isset(
$str[$i+1]) && $have_seen_non_whitespace )
                    {
// only decrement words, if there's been at least one non-space char before.
                       
--$maxwords;
                    }
                }
               
// ignore white space in a tag...
               
break;

            default:
               
$have_seen_non_whitespace = true;
                break;
        }

        if(
$maxwords < 1 )
        {    
// We have reached the cutting point:
           
break;
        }
    }

    if(
$maxwords < 1 )
    {    
// Cutting is necessary:
        // restrict content to required number of words:
       
$str = utf8_substr( $str, 0, $i );

       
// Add a cutting mark:
       
$str .= $params['cutting_mark'];

       
// balance the tags out:
       
$str = balance_tags( $str );
       
// remove empty tags:
       
$str = preg_replace( '~<([\s]+?)[^>]*?></\1>~is', '', $str );
    }

    if(
$params['always_continue'] || $maxwords < 1 )
    {
// we want a continued text if avoid_end_hellip is not set:
       
if( ! isset( $params['avoid_end_hellip'] ) )
        {
           
$str .= ' <a href="'.$params['continued_link'].'" class="'.$params['continued_class'].'">'.$params['continued_text'].'</a>';
        }
    }

    return
$str;
}


/**
 * Convert all non ASCII chars (except if UTF-8, GB2312 or CP1251) to &#nnnn; unicode references.
 * Also convert entities to &#nnnn; unicode references if output is not HTML (eg XML)
 *
 * Preserves < > and quotes.
 *
 * fplanque: simplified
 * sakichan: pregs instead of loop
 */
function convert_chars( $content, $flag = 'html' )
{
    global
$b2_htmltrans, $evo_charset;

   
/**
     * Translation of invalid Unicode references range to valid range.
     * These are Windows CP1252 specific characters.
     * They would look weird on non-Windows browsers.
     * If you've ever pasted text from MSWord, you'll understand.
     *
     * You should not have to change this.
     */
   
static $b2_htmltranswinuni = array(
       
'&#128;' => '&#8364;', // the Euro sign
       
'&#130;' => '&#8218;',
       
'&#131;' => '&#402;',
       
'&#132;' => '&#8222;',
       
'&#133;' => '&#8230;',
       
'&#134;' => '&#8224;',
       
'&#135;' => '&#8225;',
       
'&#136;' => '&#710;',
       
'&#137;' => '&#8240;',
       
'&#138;' => '&#352;',
       
'&#139;' => '&#8249;',
       
'&#140;' => '&#338;',
       
'&#142;' => '&#382;',
       
'&#145;' => '&#8216;',
       
'&#146;' => '&#8217;',
       
'&#147;' => '&#8220;',
       
'&#148;' => '&#8221;',
       
'&#149;' => '&#8226;',
       
'&#150;' => '&#8211;',
       
'&#151;' => '&#8212;',
       
'&#152;' => '&#732;',
       
'&#153;' => '&#8482;',
       
'&#154;' => '&#353;',
       
'&#155;' => '&#8250;',
       
'&#156;' => '&#339;',
       
'&#158;' => '&#382;',
       
'&#159;' => '&#376;'
   
);

   
// Convert highbyte non ASCII/UTF-8 chars to urefs:
   
if( ! in_array(strtolower($evo_charset), array( 'utf8', 'utf-8', 'gb2312', 'windows-1251') ) )
    {
// This is a single byte charset
        // fp> why do we actually bother doing this:?
       
$content = preg_replace_callback(
           
'/[\x80-\xff]/',
           
'_convert_chars_callback',
           
$content);
    }

   
// Convert Windows CP1252 => Unicode (valid HTML)
    // TODO: should this go to input conversions instead (?)
   
$content = strtr( $content, $b2_htmltranswinuni );

    if(
$flag == 'html' )
    {
// we can use entities
        // Convert & chars that are not used in an entity
       
$content = preg_replace('/&(?![#A-Za-z0-9]{2,20};)/', '&amp;', $content);
    }
    else
    {
// unicode, xml...
        // Convert & chars that are not used in an entity
       
$content = preg_replace('/&(?![#A-Za-z0-9]{2,20};)/', '&#38;', $content);

       
// Convert HTML entities to urefs:
       
$content = strtr($content, $b2_htmltrans);
    }

    return(
$content );
}


/**
 * Callback for preg_replace_callback in convert_chars()
 */
function _convert_chars_callback( $matches )
{
    return
"&#".ord( $matches[0] ).";";
}


/**
 * Get number of bytes in $string. This works around mbstring.func_overload, if
 * activated for strlen/mb_strlen.
 * @param string
 * @return int
 */
function evo_bytes( $string )
{
   
$fo = ini_get('mbstring.func_overload');
    if(
$fo && $fo & 2 && function_exists('mb_strlen') )
    {
// overloading of strlen is enabled
       
return mb_strlen( $string, 'ASCII' );
    }
    return
strlen($string);
}


/**
 * mbstring wrapper for strtolower function
 *
 * @deprecated by {@link utf8_strtolower()}
 *
 * fp> TODO: instead of those "when used" ifs, it would make more sense to redefine
 * mb_strtolower beforehand if it doesn"t exist (it would then just be a fallback
 * to the strtolower + a Debuglog->add() )
 *
 * @param string
 * @return string
 */
function evo_strtolower( $string )
{
    global
$current_charset;

    if(
$current_charset != 'iso-8859-1' && $current_charset != '' && function_exists('mb_strtolower') )
    {
        return
mb_strtolower( $string, $current_charset );
    }

    return
strtolower($string);
}


/**
 * mbstring wrapper for strlen function
 *
 * @deprecated by {@link utf8_strlen()}
 *
 * @param string
 * @return string
 */
function evo_strlen( $string )
{
    global
$current_charset;

    if(
$current_charset != 'iso-8859-1' && $current_charset != '' && function_exists('mb_strlen') )
    {
        return
mb_strlen( $string, $current_charset );
    }

    return
strlen($string);
}

/**
 * mbstring wrapper for strpos function
 *
 * @deprecated by {@link utf8_strpos()}
 *
 * @param string
 * @param string
 * @return int
 */
function evo_strpos( $string , $needle , $offset = null )
{
    global
$current_charset;

    if(
$current_charset != 'iso-8859-1' && $current_charset != '' && function_exists('mb_strpos') )
    {
        return
mb_strpos( $string, $needle, $offset ,$current_charset );
    }

    return
strpos( $string , $needle , $offset );
}


/**
 * mbstring wrapper for substr function
 *
 * @deprecated by {@link utf8_substr()}
 *
 * @param string
 * @param int start position
 * @param int string length
 * @return string
 */
function evo_substr( $string, $start = 0, $length = '#' )
{
    global
$current_charset;

    if( !
$length )
    {
// make mb_substr and substr behave consistently (mb_substr returns string for length=0)
       
return '';
    }
    if(
$length == '#' )
    {
       
$length = utf8_strlen($string);
    }

    if(
$current_charset != 'iso-8859-1' && $current_charset != '' && function_exists('mb_substr') )
    {
        return
mb_substr( $string, $start, $length, $current_charset );
    }

    return
substr( $string, $start, $length );
}


/**
 * Split $text into blocks by using $pattern and call $callback on the non-matching blocks.
 *
 * The non-matching block's text is the first param to $callback and additionally $params gets passed.
 *
 * This gets used to make links clickable or replace smilies.
 *
 * E.g., to replace only in non-HTML tags, call it like:
 * <code>callback_on_non_matching_blocks( $text, '~<[^>]*>~s', 'your_callback' );</code>
 *
 * {@internal This function gets tested in misc.funcs.simpletest.php.}}
 *
 * @param string Text to handle
 * @param string Regular expression pattern that defines blocks to exclude.
 * @param callback Function name or object/method array to use as callback.
 *               Each non-matching block gets passed as first param, additional params may be
 *               passed with $params.
 * @param array Of additional ("static") params to $callback.
 * @return string
 */
function callback_on_non_matching_blocks( $text, $pattern, $callback, $params = array() )
{
    global
$evo_non_matching_blocks;

    if(
preg_match_all( $pattern, $text, $matches, PREG_OFFSET_CAPTURE | PREG_PATTERN_ORDER ) )
    {    
// $pattern matches, call the callback method on full text except of matching blocks

        // Create an unique string in order to replace all matching blocks temporarily
       
$unique_replacement = md5( time() + rand() );

        if( ! isset(
$evo_non_matching_blocks ) )
        {    
// Init cache array once:
           
$evo_non_matching_blocks = array();
        }
       
// Use level in order to don't mix from other recursive calls:
       
$level = count( $evo_non_matching_blocks );
       
$evo_non_matching_blocks[ $level ] = array();

        foreach(
$matches[0] as $l => $l_matching )
        {    
// Build arrays with a source code of the matching blocks and with temporary replacement
           
if( substr( $l_matching[0], 1, 1 ) == '[' )
            {    
// Exception for match out short tags,
                // We should not replace a char(usually a space) before short tag like [image:123]:
               
$l_matching[0] = substr( $l_matching[0], 1 );
            }
           
$evo_non_matching_blocks[ $level ][ '?'.$l.$unique_replacement.$l.'?' ] = $l_matching[0];
        }

       
// Replace all matching blocks with temporary text like '?X219a33da9c1b8f4e335bffc015df8c96X?'
        // where X is index of match block in array $matches[0]
        // It is used to avoid any changes in the matching blocks
       
$text = str_replace( $evo_non_matching_blocks[ $level ], array_keys( $evo_non_matching_blocks[ $level ] ), $text );

       
// Callback:
       
$callback_params = $params;
       
array_unshift( $callback_params, $text );
       
$text = call_user_func_array( $callback, $callback_params );

       
// Revert a source code of the matching blocks in content
       
$text = str_replace( array_keys( $evo_non_matching_blocks[ $level ] ), $evo_non_matching_blocks[ $level ], $text );

        return
$text;
    }

   
$callback_params = $params;
   
array_unshift( $callback_params, $text );
    return
call_user_func_array( $callback, $callback_params );
}


/**
 * Replace non matching blocks from temp strings like '?X219a33da9c1b8f4e335bffc015df8c96X?' back to original string like '<code>$some_var = "some value";</code>'
 *
 * @param array Matches
 * @return array Matches
 */
function fix_non_matching_blocks( $matches )
{
    global
$evo_non_matching_blocks;

    if( !
is_array( $matches ) )
    {    
// This function works only with array of matches
       
return $matches;
    }

    if( ! isset(
$evo_non_matching_blocks ) ||
        !
is_array( $evo_non_matching_blocks ) )
    {    
// Nothing to fix, Return source param value:
       
return $matches;
    }

   
$level = count( $evo_non_matching_blocks ) - 1;

    if( empty(
$evo_non_matching_blocks[ $level ] ) )
    {    
// Nothing to fix, Return source param value:
       
return $matches;
    }

    foreach(
$matches as $m => $match )
    {
        if(
is_array( $match ) )
        {    
// Fix recursively:
           
foreach( $match as $s => $sub_match )
            {
               
$matches[ $m ][ $s ] = str_replace( array_keys( $evo_non_matching_blocks[ $level ] ), $evo_non_matching_blocks[ $level ], $sub_match );
            }
        }
        else
        {    
// Revert back to original values from temp strings:
           
$matches[ $m ] = str_replace( array_keys( $evo_non_matching_blocks[ $level ] ), $evo_non_matching_blocks[ $level ], $match );
        }
    }

    return
$matches;
}


/**
 * Perform a global regular expression match outside of blocks <code></code>, <pre></pre>, markdown codeblocks ``
 *
 * @param string Pattern to search for
 * @param string Content
 * @param array Array of all matches in multi-dimensional array
 * @return integer|boolean Number of full pattern matches (which might be zero), or FALSE if an error occurred.
 */
function preg_match_outcode( $search, $content, & $matches )
{
    if(
stristr( $content, '<code' ) !== false || stristr( $content, '<pre' ) !== false || strstr( $content, '`' ) !== false )
    {    
// Call preg_match_all() on everything outside code/pre and markdown codeblocks:
       
$result = callback_on_non_matching_blocks( $content,
           
'~(`|<(code|pre)[^>]*>).*?(\1|</\2>)~is',
           
'preg_match_outcode_callback', array( $search, & $matches ) );
       
// Revert codeblocks back if they are located inside search regexp:
       
$matches = fix_non_matching_blocks( $matches );
        return
$result;
    }
    else
    {    
// No code/pre blocks, search in the whole thing:
       
return preg_match_all( $search, $content, $matches );
    }
}


/**
 * Used for function callback_on_non_matching_blocks(), because there is different order of params
 *
 * @param string Pattern to search for
 * @param string Content
 * @param array Array of all matches in multi-dimensional array
 * @return integer|boolean Number of full pattern matches (which might be zero), or FALSE if an error occurred.
 */
function preg_match_outcode_callback( $content, $search, & $matches )
{
    return
preg_match_all( $search, $content, $matches );
}


/**
 * Replace content outside of blocks <code></code>, <pre></pre>, markdown codeblocks ``
 *
 * @deprecated Use new function replace_outside_code_tags()
 *
 * @param array|string Search list
 * @param array|string Replace list or Callback function
 * @param string Source content
 * @param string Callback function name
 * @param string Type of callback function: 'preg' -> preg_replace(), 'preg_callback' -> preg_replace_callback(), 'str' -> str_replace() (@see replace_content())
 * @return string Replaced content
 */
function replace_content_outcode( $search, $replace, $content, $replace_function_callback = 'replace_content', $replace_function_type = 'preg' )
{
    return
replace_outside_code_tags( $search, $replace, $content, $replace_function_callback, $replace_function_type );
}


/**
 * Replace content outside of blocks <code></code>, <pre></pre>, markdown codeblocks ``
 *
 * @param array|string Search list
 * @param array|string Replace list or Callback function
 * @param string Source content
 * @param string Callback function name
 * @param string Type of callback function: 'preg' -> preg_replace(), 'preg_callback' -> preg_replace_callback(), 'str' -> str_replace() (@see replace_content())
 * @return string Replaced content
 */
function replace_outside_code_tags( $search, $replace, $content, $replace_function_callback = 'replace_content', $replace_function_type = 'preg' )
{
    if( ! empty(
$search ) )
    {
        if(
stristr( $content, '<code' ) !== false ||
           
stristr( $content, '<pre' ) !== false ||
           
strstr( $content, '`' ) !== false )
        {    
// Call replace_content() on everything outside code/pre, and markdown codeblocks:
           
$content = callback_on_non_matching_blocks( $content,
               
'~(`.*?`|'
               
.'<code[^>]*>.*?</code>|'
               
.'<pre[^>]*>.*?</pre>)~is',
               
$replace_function_callback, array( $search, $replace, $replace_function_type ) );
        }
        else
        {    
// No code/pre blocks, replace on the whole thing
           
$content = call_user_func( $replace_function_callback, $content, $search, $replace, $replace_function_type );
        }
    }

    return
$content;
}



/**
 * Replace content outside of blocks <code></code>, <pre></pre>, markdown codeblocks `` AND also outside of short tags like [image:123]
 *
 * @deprecated Use new function replace_outside_code_and_short_tags()
 *
 * @param array|string Search list
 * @param array|string Replace list or Callback function
 * @param string Source content
 * @param string Callback function name
 * @param string Type of callback function: 'preg' -> preg_replace(), 'preg_callback' -> preg_replace_callback(), 'str' -> str_replace() (@see replace_content())
 * @return string Replaced content
 */
function replace_content_outcode_shorttags( $search, $replace, $content, $replace_function_callback = 'replace_content', $replace_function_type = 'preg' )
{
    return
replace_outside_code_and_short_tags( $search, $replace, $content, $replace_function_callback, $replace_function_type );
}


/**
 * Replace content outside of blocks <code></code>, <pre></pre>, markdown codeblocks `` AND also outside of short tags like [image:123]
 *
 * @param array|string Search list
 * @param array|string Replace list or Callback function
 * @param string Source content
 * @param string Callback function name
 * @param string Type of callback function: 'preg' -> preg_replace(), 'preg_callback' -> preg_replace_callback(), 'str' -> str_replace() (@see replace_content())
 * @return string Replaced content
 */
function replace_outside_code_and_short_tags( $search, $replace, $content, $replace_function_callback = 'replace_content', $replace_function_type = 'preg' )
{
    if( ! empty(
$search ) )
    {
        if(
stristr( $content, '<code' ) !== false ||
           
stristr( $content, '<pre' ) !== false ||
           
strstr( $content, '`' ) !== false ||
           
preg_match( '/\[[a-z]+:[^\]`]+\]/i', $content ) )
        {    
// Call replace_content() on everything outside code/pre, markdown codeblocks and short tags:
           
$content = callback_on_non_matching_blocks( $content,
               
'~(`.*?`|'
               
.'<code[^>]*>.*?</code>|'
               
.'<pre[^>]*>.*?</pre>|'
               
.'[^\[]\[[a-z]+:[^\]`]+\])~is',
               
$replace_function_callback, array( $search, $replace, $replace_function_type ) );
        }
        else
        {    
// No code/pre blocks, replace on the whole thing
           
$content = call_user_func( $replace_function_callback, $content, $search, $replace, $replace_function_type );
        }
    }

    return
$content;
}


/**
 * Replace content, Used for function callback_on_non_matching_blocks(), because there is different order of params
 *
 * @param string Source content
 * @param array|string Search list
 * @param array|string Replace list
 * @param string Type of function: 'preg' -> preg_replace(), 'preg_callback' -> preg_replace_callback(), 'str' -> str_replace()
 * @param string The maximum possible replacements for each pattern in each subject string. Defaults to -1 (no limit).
 * @return string Replaced content
 */
function replace_content( $content, $search, $replace, $type = 'preg', $limit = -1 )
{
    if(
$limit == 0 )
    {    
// Strange request to nothing replace, Return original content:
       
return $content;
    }

    switch(
$type )
    {
        case
'str':
            if(
$limit == -1 )
            {    
// Unlimited replace:
               
return str_replace( $search, $replace, $content );
            }
            else
            {    
// Limited replace:
               
$pos = strpos( $content, $search );
                if(
$pos !== false )
                {    
// Do the limited replacements:
                   
for( $p = 0; $p < $limit; $p++ )
                    {
                        if(
$pos === false )
                        {    
// Stop searching:
                           
break;
                        }
                       
$content = substr_replace( $content, $replace, $pos, strlen( $search ) );
                       
// Go to next searched substring:
                       
$pos = strpos( $content, $search, $pos + strlen( $replace ) );
                    }
                }
                return
$content;
            }

        case
'preg_callback':
            return
preg_replace_callback( $search, $replace, $content, $limit );

        default:
// 'preg'
           
return preg_replace( $search, $replace, $content, $limit );
    }
}


/**
 * Replace content by callback, Used for function callback_on_non_matching_blocks(), because there is different order of params
 *
 * @param string Source content
 * @param array|string Search list
 * @param array|string Replace callback
 * @return string Replaced content
 */
function replace_content_callback( $content, $search, $replace_callback )
{
    global
$evo_replace_outside_code_tags_callback;

   
// Store here the requested callback function in order to use pre-processor function fix_replace_content_callback() before we call the original requested callback function:
   
$evo_replace_outside_code_tags_callback = $replace_callback;

    return
preg_replace_callback( $search, 'fix_replace_content_callback', $content );
}


/**
 * Replace non matching blocks from temp strings like '?X219a33da9c1b8f4e335bffc015df8c96X?' back to original string like '<code>$some_var = "some value";</code>'
 *
 * @param Array Matches
 * @return mixed
 */
function fix_replace_content_callback( $m )
{
    global
$evo_replace_outside_code_tags_callback;

   
// Replace non matching blocks from temp strings like '?X219a33da9c1b8f4e335bffc015df8c96X?' back to original string like '<code>$some_var = "some value";</code>'
   
$m = fix_non_matching_blocks( $m );

   
// Call real function with fixed blocks in matches:
   
$result = call_user_func_array( $evo_replace_outside_code_tags_callback, array( $m ) );

   
// Clear temp var:
   
unset( $evo_replace_outside_code_tags_callback );

    return
$result;
}


/**
 * Split a content by separators outside <code> and <pre> blocks
 *
 * @param string|array Separators
 * @param string Content
 * @param boolean TRUE - parenthesized expression of separator will be captured and returned as well
 * @return array The result of explode() function
 */
function split_outcode( $separators, $content, $capture_separator = false )
{
   
// Check if the separators exists in content
   
if( ! is_array( $separators ) )
    {
// Convert string to array with one element
       
$separators = array( $separators );
    }
   
$separators_exists = false;
    if(
is_array( $separators ) )
    {
// Find in array
       
foreach( $separators as $separator )
        {
            if(
strpos( $content, $separator ) !== false )
            {
// Separator is found
               
$separators_exists = true;
                break;
            }
        }
    }

    if(
$separators_exists )
    {
// There are separators in content, Split the content:

        // Initialize temp values for replace the separators
       
if( $capture_separator )
        {
           
$rplc_separators = array();
            foreach(
$separators as $s => $separator )
            {
               
$rplc_separators[] = '#separator'.$s.'='.md5( rand() ).'#';
            }
        }
        else
        {
           
$rplc_separators = '#separator='.md5( rand() ).'#';
        }
       
// Replace the content separators with temp value
       
if( strpos( $content, '<code' ) !== false || strpos( $content, '<pre' ) !== false )
        {
// Call replace_separators_callback() on everything outside code/pre:
           
$content = callback_on_non_matching_blocks( $content,
               
'~<(code|pre)[^>]*>.*?</\1>~is',
               
'replace_content', array( $separators, $rplc_separators, 'str' ) );
        }
        else
        {
// No code/pre blocks, replace on the whole thing
           
$content = str_replace( $separators, $rplc_separators, $content );
        }

        if(
$capture_separator )
        {
// Save the separators
           
$split_regexp = '~('.implode( '|', $rplc_separators ).')~s';
           
$content_parts = preg_split( $split_regexp, $content, -1, PREG_SPLIT_DELIM_CAPTURE );
            foreach(
$content_parts as $c => $content_part )
            {
                if( (
$s = array_search( $content_part, $rplc_separators ) ) !== false )
                {
// Replace original separator back
                   
$content_parts[ $c ] = $separators[ $s ];
                }
            }
            return
$content_parts;
        }
        else
        {
// Return only splitted content(without separators)
           
return explode( $rplc_separators, $content );
        }
    }
    else
    {
// No separators in content, Return whole content as one element of array
       
return array( $content );
    }
}


/**
 * Remove short tags like [image:], [video:] and etc. that are inside <p> blocks and before <br> and move them before the paragraph
 *
 * @param string Source content
 * @param string Search pattern
 * @param function Optional callback function that accepts search pattern and current paragraph as arguments and returns the new_paragraph
 * @param array Params
 * @return string Content
 */
function move_short_tags( $content, $pattern = NULL, $callback = NULL, $params = array() )
{
   
$params = array_merge( array(
           
'check_code_block' => true,
        ),
$params );

    if( isset(
$params['check_code_block'] ) && $params['check_code_block'] && ( ( stristr( $content, '<code' ) !== false ) || ( stristr( $content, '<pre' ) !== false ) ) )
    {    
// Call $this->render_collection_data() on everything outside code/pre:
       
$params['check_code_block'] = false;
       
$content = callback_on_non_matching_blocks( $content,
           
'~<(code|pre)[^>]*>.*?</\1>~is',
           
'move_short_tags', array( $pattern, $callback, $params ) );
        return
$content;
    }

   
// Get individual paragraphs:
   
preg_match_all( '#(<p[\s*|>])?.*?<(/p|br\s?/?)>#i', $content, $paragraphs );

    if(
is_null( $pattern ) )
    {    
// Default pattern:
       
$pattern = '#\[(image|video|audio|include|cblock|/?div|(parent:|item:[^:\]]+:)?(subscribe|emailcapture|compare|fields)):[^\]]+\]#i';
    }

    foreach(
$paragraphs[0] as $i => $current_paragraph )
    {
        if(
$callback )
        {
           
$new_paragraph = call_user_func( $callback, $pattern, $current_paragraph );
        }
        else
        {
           
// get short tags in each paragraph
           
preg_match_all( $pattern, $current_paragraph, $matches );
           
$new_paragraph = $current_paragraph;

            if(
$matches[0] )
            {
               
$new_paragraph = str_replace( $matches[0], '', $current_paragraph );

               
// convert &nbsp; to space
               
$x = str_replace( "\xC2\xA0", ' ', $new_paragraph );

                if(
preg_match( '#^(<p[\s*|>])?\s*<(/p|br\s?/?)>$#i', $x ) === 1 )
                {    
// Remove paragraph the if moving out the short tag will result to an empty paragraph:
                   
$new_paragraph = '';
                }

               
$new_paragraph = implode( '', $matches[0] ).$new_paragraph;
            }
        }
       
$content = str_replace( $current_paragraph, $new_paragraph, $content );
    }

    return
$content;
}


/**
 * Make links clickable in a given text.
 *
 * It replaces only text which is not between <a> tags already.
 *
 * @todo dh> this should not replace links in tags! currently fails for something
 *           like '<img src=" http://example.com/" />' (not usual though!)
 * fp> I am trying to address this by not replacing anything inside tags
 * fp> This should be replaced by a clean state machine (one single variable for current state)
 *
 * {@internal This function gets tested in misc.funcs.simpletest.php.}}
 *
 * @param string Text
 * @param string Url delimeter
 * @param string Callback function name
 * @param string Additional attributes for tag <a>
 * @param boolean TRUE to exclude links from header tags like h1, h2, etc.
 * @return string
 */
function make_clickable( $text, $moredelim = '&amp;', $callback = 'make_clickable_callback', $additional_attrs = '', $exclude_headers = false )
{
   
$r = '';
   
$inside_bracket_short_tag = false;
   
$inside_tag = false;
   
$in_a_tag = false;
   
$in_code_tag = false;
   
$in_tag_quote = false;
   
$in_header_tag = false;
   
$from_pos = 0;
   
$i = 0;
   
$n = strlen($text);

   
// Not using callback_on_non_matching_blocks(), because it requires
    // wellformed HTML and the implementation below should be
    // faster and less memory intensive (tested for some example content)
   
while( $i < $n )
    {    
// Go through each char in string... (we will fast forward from tag to tag)
       
if( $inside_bracket_short_tag )
        {    
// State: We're currently inside bracket short tag like [image:], [emailcapture:], [fields:], [compare:] and etc.:
           
if( $text[ $i ] == ']' )
            {    
// End of bracket short tag:
               
$inside_bracket_short_tag = false;
               
$r .= substr( $text, $from_pos, $i - $from_pos + 1 );
               
$from_pos = $i + 1;
            }
        }
        elseif(
$inside_tag )
        {    
// State: We're currently inside some tag:
           
switch( $text[$i] )
            {
                case
'>':
                    if(
$in_tag_quote )
                    {
// This is in a quoted string so it doesn't really matter...
                       
break;
                    }
                   
// end of tag:
                   
$inside_tag = false;
                   
$r .= substr($text, $from_pos, $i-$from_pos+1);
                   
$from_pos = $i+1;
                   
// $r .= '}';
                   
break;

                case
'"':
                case
'\'':
                   
// This is the beginning or the end of a quoted string:
                   
if( ! $in_tag_quote )
                    {
                       
$in_tag_quote = $text[$i];
                    }
                    elseif(
$in_tag_quote == $text[$i] )
                    {
                       
$in_tag_quote = false;
                    }
                    break;
            }
        }
        elseif(
$in_a_tag )
        {    
// In a link but no longer inside <a>...</a> tag or any other embedded tag like <strong> or whatever
           
switch( $text[$i] )
            {
                case
'<':
                    if(
strtolower(substr($text, $i+1, 3)) == '/a>' )
                    {    
// Ok, this is the end tag of the link:
                        // $r .= substr($text, $from_pos, $i-$from_pos+4);
                        // $from_pos = $i+4;
                       
$i += 4;
                       
// pre_dump( 'END A TAG: '.substr($text, $from_pos, $i-$from_pos) );
                       
$r .= substr($text, $from_pos, $i-$from_pos);
                       
$from_pos = $i;
                       
$in_a_tag = false;
                       
$in_tag_quote = false;
                       
$in_header_tag = false;
                    }
                    break;
            }
        }
        elseif(
$in_code_tag )
        {    
// In a code but no longer inside <code>...</code> tag or any other embedded tag like <strong> or whatever
           
switch( $text[$i] )
            {
                case
'<':
                    if(
strtolower(substr($text, $i+1, 5)) == '/code' )
                    {    
// Ok, this is the end tag of the code:
                        // $r .= substr($text, $from_pos, $i-$from_pos+4);
                        // $from_pos = $i+4;
                       
$i += 7;
                       
// pre_dump( 'END A TAG: '.substr($text, $from_pos, $i-$from_pos) );
                       
$r .= substr($text, $from_pos, $i-$from_pos);
                       
$from_pos = $i;
                       
$in_code_tag = false;
                       
$in_tag_quote = false;
                       
$in_header_tag = false;
                    }
                    break;
            }
        }
        elseif(
$in_header_tag )
        {    
// In a code but no longer inside <h#>...</h#> tags or any other embedded tag like <strong> or whatever
           
switch( $text[$i] )
            {
                case
'<':
                    if(
strtolower( substr( $text, $i+1, 3 ) ) == '/'.$in_header_tag )
                    {    
// Ok, this is the end tag of the header:
                       
$i += 5;
                       
$r .= substr( $text, $from_pos, $i - $from_pos );
                       
$from_pos = $i;
                       
$in_code_tag = false;
                       
$in_tag_quote = false;
                       
$in_header_tag = false;
                    }
                    break;
            }
        }
        else
        {
// State: we're not currently in any tag:
            // Find next tag opening:
           
if( ( $b = strpos( $text, '[', $i ) ) !== false &&
               
$b < strpos( $text, '<', $i ) )
            {    
// Check if a bracket '[]' short tag is really opening but not after html tag:
                // (we are finding here short tags like [image:], [emailcapture:], [fields:], [compare:] and etc., see full list in the Item->render_inline_tags())
               
$start_b = $b;
               
$b++;
               
$short_tag_name = '';
                while( isset(
$text[ $b ] ) && $text[ $b ] != ':' && $text[ $b ] != ']' )
                {    
// Get short tag name between chars '[' and ( ':' or ']' ):
                   
$short_tag_name .= $text[ $b ];
                   
$b++;
                }
               
// We are inside bracket short tag if its name contains only letters:
               
$inside_bracket_short_tag = preg_match( '/^[a-z]+$/', $short_tag_name );
                if(
$inside_bracket_short_tag )
                {    
// Set index to call user func below between two inline short tags:
                   
$i = $start_b;
                }
            }

            if( !
$inside_bracket_short_tag )
            {    
// Try to find opening HTML tag only if bracket short tag is not opened currently:
               
$i = strpos($text, '<', $i);
                if(
$i === false )
                {
// No more opening tags:
                   
break;
                }

               
$inside_tag = true;
               
$in_tag_quote = false;
               
// s$r .= '{'.$text[$i+1];

               
if( ($text[$i+1] == 'a' || $text[$i+1] == 'A') && ctype_space($text[$i+2]) )
                {
// opening "A" tag
                   
$in_a_tag = true;
                }

                if( (
substr( $text, $i+1, 4 ) == 'code') )
                {
// opening "code" tag
                   
$in_code_tag = true;
                }

                if(
$exclude_headers && preg_match( '/^h[1-6]$/', substr( $text, $i+1, 2 ), $h_match ) )
                {    
// opening "h1" - "h6" tags:
                   
$in_header_tag = $h_match[0];
                }
            }

           
// Make the text before the opening < clickable:
           
$r .= call_user_func_array( $callback, array( substr( $text, $from_pos, $i-$from_pos ), $moredelim, $additional_attrs ) );
           
$from_pos = $i;

           
// $i += 2;
       
}

       
$i++;
    }

   
// the remaining part:
   
if( $in_a_tag )
    {
// may happen for invalid html:
       
$r .= substr($text, $from_pos);
    }
    else
    {    
// Make remplacements in the remaining part:
       
$r .= call_user_func_array( $callback, array( substr( $text, $from_pos ), $moredelim, $additional_attrs ) );
    }

    return
$r;
}


/**
 * Callback function for {@link make_clickable()}.
 *
 * original function: phpBB, extended here for AIM & ICQ
 * fplanque restricted :// to http:// and mailto://
 * Fixed to not include trailing dot and comma.
 *
 * fp> I'm thinking of moving this into the autolinks plugin (only place where it's used)
 *     and break it up into something more systematic.
 *
 * @param string Text
 * @param string Url delimeter
 * @param string Additional attributes for tag <a>
 * @return string The clickable text.
 */
function make_clickable_callback( $text, $moredelim = '&amp;', $additional_attrs = '' )
{
    if( !empty(
$additional_attrs ) )
    {
       
$additional_attrs = ' '.trim( $additional_attrs );
    }

   
// Add style class to break long urls:
   
$additional_attrs = stripos( $additional_attrs, ' class="' ) === false
       
? $additional_attrs.' class="linebreak"'
       
: preg_replace( '/ class="([^"]*)"/i', ' class="$1 linebreak"', $additional_attrs );

   
$pattern_domain = '([\p{L}0-9\-]+\.[\p{L}0-9\-.\~]+)'; // a domain name (not very strict)
   
$text = preg_replace(
       
/* Tblue> I removed the double quotes from the first RegExp because
                  it made URLs in tag attributes clickable.
                  See http://forums.b2evolution.net/viewtopic.php?p=92073 */
       
array( '#(^|[\s>\(]|\[url=)(https?|mailto)://([^"<>{}\s]+[^".,:;!\?<>{}\s\]\)])#i',
           
'#(^|[\s>\(]|\[url=)aim:([^",<\s\]\)]+)#i',
           
'#(^|[\s>\(]|\[url=)icq:(\d+)#i',
           
'#(^|[\s>\(]|\[url=)www\.'.$pattern_domain.'([^"<>{}\s]*[^".,:;!\?\s\]\)])#i',
           
'#(^|[\s>\(]|\[url=)([a-z0-9\-_.]+?)@'.$pattern_domain.'([^".,:;!\?&<\s\]\)]+)#i', ),
        array(
'$1<a href="$2://$3"'.$additional_attrs.'>$2://$3</a>',
           
'$1<a href="aim:goim?screenname=$2$3'.$moredelim.'message='.rawurlencode(T_('Hello')).'"'.$additional_attrs.'>$2$3</a>',
           
'$1<a href="http://wwp.icq.com/scripts/search.dll?to=$2"'.$additional_attrs.'>$2</a>',
           
'$1<a href="http://www.$2$3$4"'.$additional_attrs.'>www.$2$3$4</a>',
           
'$1<a href="mailto:$2@$3$4"'.$additional_attrs.'>$2@$3$4</a>', ),
       
$text );

    return
$text;
}


/***** // Formatting functions *****/

/**
 * Convert timestamp to MySQL/ISO format.
 *
 * @param integer UNIX timestamp
 * @return string Date formatted as "Y-m-d H:i:s"
 */
function date2mysql( $ts )
{
    if(
$ts > 0 )
    {    
// Allow only positive timestamp value:
       
return date( 'Y-m-d H:i:s', $ts );
    }
    else
    {    
// If timestamp is wrong(NULL or FALSE or negative value) use this mimimum date instead of default 1970-01-01 00:00:00,
        // because with negative timezone like -2:00 it may returns 1969-12-31 22:00:00 which creates MySQL error "Incorrect datetime value".
       
return '2000-01-01 00:00:00';
    }
}

/**
 * Convert a MYSQL date to a UNIX timestamp.
 *
 * @param string Date formatted as "Y-m-d H:i:s"
 * @param boolean true to use GM time
 * @return integer UNIX timestamp
 */
function mysql2timestamp( $m, $useGM = false )
{
   
$func = $useGM ? 'gmmktime' : 'mktime';
    return
$func(
       
intval( substr( $m, 11, 2 ) ),  // hour
       
intval( substr( $m, 14, 2 ) ),  // minute
       
intval( substr( $m, 17, 2 ) ),  // second
       
intval( substr( $m, 5, 2 ) ),   // month
       
intval( substr( $m, 8, 2 ) ),   // day
       
intval( substr( $m, 0, 4 ) ) ); // year
}

/**
 * Convert a MYSQL date -- WITHOUT the time -- to a UNIX timestamp
 *
 * @param string Date formatted as "Y-m-d"
 * @param boolean true to use GM time
 * @return integer UNIX timestamp
 */
function mysql2datestamp( $m, $useGM = false )
{
   
$func = $useGM ? 'gmmktime' : 'mktime';
    return
$func( 0, 0, 0, substr($m,5,2), substr($m,8,2), substr($m,0,4) );
}

/**
 * Format a MYSQL date to current locale date format.
 *
 * @param string MYSQL date YYYY-MM-DD HH:MM:SS
 */
function mysql2localedate( $mysqlstring )
{
    return
mysql2date( locale_datefmt(), $mysqlstring );
}

function
mysql2localetime( $mysqlstring )
{
    return
mysql2date( locale_timefmt(), $mysqlstring );
}

function
mysql2localedatetime( $mysqlstring )
{
    return
mysql2date( locale_datefmt().' '.locale_timefmt(), $mysqlstring );
}

function
mysql2localedatetime_spans( $mysqlstring, $datefmt = NULL, $timefmt = NULL )
{
    if(
is_null( $datefmt ) )
    {
       
$datefmt = locale_datefmt();
    }
    if(
is_null( $timefmt ) )
    {
       
$timefmt = locale_timefmt();
    }

    return
'<span class="date">'
                   
.mysql2date( $datefmt, $mysqlstring )
                    .
'</span> <span class="time">'
                   
.mysql2date( $timefmt, $mysqlstring )
                    .
'</span>';
}


/**
 * Format a MYSQL date.
 *
 * @param string enhanced format string
 * @param string MYSQL date YYYY-MM-DD HH:MM:SS
 * @param boolean true to use GM time
 */
function mysql2date( $dateformatstring, $mysqlstring, $useGM = false )
{
   
$m = $mysqlstring;
    if( empty(
$m) || ($m == '0000-00-00 00:00:00' ) )
        return
false;

   
// Get a timestamp:
   
$unixtimestamp = mysql2timestamp( $m );

    return
date_i18n( $dateformatstring, $unixtimestamp, $useGM );
}


/**
 * Date internationalization: same as date() formatting but with i18n support.
 *
 * @todo dh> support for MySQL date format instead of $unixtimestamp? This would simplify callees, where currently mktime() is used.
 * @param string enhanced format string
 * @param integer UNIX timestamp
 * @param boolean true to use GM time
 */
function date_i18n( $dateformatstring, $unixtimestamp, $useGM = false )
{
    global
$month, $month_abbrev, $weekday, $weekday_abbrev, $weekday_letter;
    global
$localtimenow, $time_difference;

    if(
$dateformatstring == 'isoZ' )
    {
// full ISO 8601 format
       
$dateformatstring = 'Y-m-d\TH:i:s\Z';
    }

    if(
$useGM )
    {
// We want a Greenwich Meridian time:
        // TODO: dh> what's the point of the substraction? UNIX timestamp should contain no time_difference in the first place?! Otherwise it should be substracted for !$useGM, too.
        // TODO: dh> Why does $useGM do not get the special symbols handling?
       
$r = gmdate($dateformatstring, ($unixtimestamp - $time_difference));
    }
    else
    {
// We want default timezone time:

        /*
        Special symbols:
            'b': wether it's today (1) or not (0)
            'l': weekday
            'D': weekday abbrev
            'e': weekday letter
            'F': month
            'M': month abbrev
        */

        #echo $dateformatstring, '<br />';

        // protect special symbols, that date() would need proper locale set for
       
$protected_dateformatstring = preg_replace( '/(?<!\\\)([blDeFM])/', '@@@\\\$1@@@', $dateformatstring );

       
#echo $protected_dateformatstring, '<br />';

       
$r = date( $protected_dateformatstring, $unixtimestamp );

        if(
$protected_dateformatstring != $dateformatstring )
        {
// we had special symbols, replace them

           
$istoday = ( date('Ymd',$unixtimestamp) == date('Ymd',$localtimenow) ) ? '1' : '0';
           
$datemonth = date('m', $unixtimestamp);
           
$dateweekday = date('w', $unixtimestamp);

           
// replace special symbols
           
$r = str_replace( array(
                       
'@@@b@@@',
                       
'@@@l@@@',
                       
'@@@D@@@',
                       
'@@@e@@@',
                       
'@@@F@@@',
                       
'@@@M@@@',
                        ),
                    array(
$istoday,
                       
trim(T_($weekday[$dateweekday])),
                       
trim(T_($weekday_abbrev[$dateweekday])),
                       
trim(T_($weekday_letter[$dateweekday])),
                       
trim(T_($month[$datemonth])),
                       
trim(T_($month_abbrev[$datemonth])) ),
                   
$r );
        }
    }

    return
$r;
}


/**
 * Add given # of days to a timestamp
 *
 * @param integer timestamp
 * @param integer days
 * @return integer timestamp
 */
function date_add_days( $timestamp, $days )
{
    return
mktime( date('H',$timestamp), date('m',$timestamp), date('s',$timestamp),
                               
date('m',$timestamp), date('d',$timestamp)+$days, date('Y',$timestamp)  );
}

/**
 * Format dates into a string in a way similar to sprintf()
 */
function date_sprintf( $string, $timestamp )
{
    global
$date_sprintf_timestamp;
   
$date_sprintf_timestamp = $timestamp;

    return
preg_replace_callback( '/%\{(.*?)\}/', 'date_sprintf_callback', $string );
}

function
date_sprintf_callback( $matches )
{
    global
$date_sprintf_timestamp;

    return
date_i18n( $matches[1], $date_sprintf_timestamp );
}


/**
 * Get date name when date was happened
 *
 * @param integer Timestamp
 * @return string Name of date (Today, Yesterday, x days ago, x months ago, x years ago)
 */
function date_ago( $timestamp )
{
    global
$servertimenow;

   
$days = floor( ( $servertimenow - $timestamp ) / 86400 );
   
$months = ceil( $days / 31 );

    if(
$days < 1 )
    {    
// Today
       
return T_('Today');
    }
    elseif(
$days == 1 )
    {    
// Yesterday
       
return T_('Yesterday');
    }
    elseif(
$days > 1 && $days <= 31 )
    {    
// Days
       
return sprintf( T_('%s days ago'), $days );
    }
    elseif(
$days > 31 && $months <= 12 )
    {    
// Months
       
return sprintf( $months == 1 ? T_('%s month ago') : T_('%s months ago'), $months );
    }
    else
    {    
// Years
       
$years = floor( $months / 12 );
        return
sprintf( $years == 1 ? T_('%s year ago') : T_('%s years ago'), $years );
    }
}


/**
 * Convert seconds to readable period
 *
 * @param integer Seconds
 * @param boolean TRUE to use short format(only first letter of period name)
 * @return string Readable time period
 */
function seconds_to_period( $seconds, $short_format = false )
{
   
$periods = array(
        array(
31536000, T_('1 year'),   T_('%s years'),   /* TRANS: Short for "1year" */  T_('%sy') ), // 365 days
       
array( 2592000,  T_('1 month'),  T_('%s months'),  /* TRANS: Short for "1month" */ T_('%sm') ), // 30 days
       
array( 86400,    T_('1 day'),    T_('%s days'),    /* TRANS: Short for "1day" */   T_('%sd') ),
        array(
3600,     T_('1 hour'),   T_('%s hours'),   /* TRANS: Short for "1hour" */  T_('%sh') ),
        array(
60,       T_('1 minute'), T_('%s minutes'), /* TRANS: Short for "1minute" */T_('%smn') ),
        array(
1,        T_('1 second'), T_('%s seconds'), /* TRANS: Short for "1second" */T_('%ss') ),
    );

    foreach(
$periods as $p_info )
    {
       
$period_value = intval( $seconds / $p_info[0] * 10 ) /10;
        if(
$period_value >= 1 )
        {
// Stop on this period
           
if( $short_format )
            {    
// Use short format:
               
$period_text = sprintf( $p_info[3], $period_value );
            }
            elseif(
$period_value == 1 )
            {
// One unit of period
               
$period_text = $p_info[1];
            }
            else
            {
// Two and more units of period
               
$period_text = sprintf( $p_info[2], $period_value );
            }
            break;
        }
    }

    if( !isset(
$period_text ) )
    {
// 0 seconds
       
$period_text = $short_format ? sprintf( T_('%ss'), 0 ) : sprintf( T_('%s seconds'), 0 );
    }

    return
$period_text;
}


/**
 * Converts an ISO 8601 date to MySQL DateTime format.
 *
 * @param string date and time in ISO 8601 format {@link http://en.wikipedia.org/wiki/ISO_8601}.
 * @return string date and time in MySQL DateTime format Y-m-d H:i:s.
 */
function iso8601_to_datetime( $iso_date )
{
    return
preg_replace('#([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(Z|[\+|\-][0-9]{2,4}){0,1}#', '$1-$2-$3 $4:$5:$6', $iso_date);
}


/**
 * Converts a MySQL DateTime to ISO 8601 date format.
 *
 * @param string date and time in MySQL DateTime format Y-m-d H:i:s
 * @return string date and time in ISO 8601 format {@link http://en.wikipedia.org/wiki/ISO_8601}.
 */
function datetime_to_iso8601( $datetime, $useGM = false )
{
   
$iso_date = mysql2date('U', $datetime);

    if(
$useGM )
    {
       
$iso_date = gmdate('Ymd', $iso_date).'T'.gmdate('H:i:s', $iso_date);
    }
    else
    {
       
$iso_date = date('Ymd', $iso_date).'T'.date('H:i:s', $iso_date);
    }

    return
$iso_date;
}


/**
 *
 * @param integer year
 * @param integer month (0-53)
 * @param integer 0 for sunday, 1 for monday
 */
function get_start_date_for_week( $year, $week, $startofweek )
{
   
$new_years_date = mktime( 0, 0, 0, 1, 1, $year );
   
$weekday = date('w', $new_years_date);
   
// echo '<br> 1st day is a: '.$weekday;

    // How many days until start of week:
   
$days_to_new_week = (7 - $weekday + $startofweek) % 7;
   
// echo '<br> days to new week: '.$days_to_new_week;

    // We now add the required number of days to find the 1st sunday/monday in the year:
    //$first_week_start_date = $new_years_date + $days_to_new_week * 86400;
    //echo '<br> 1st week starts on '.date( 'Y-m-d H:i:s', $first_week_start_date );

    // We add the number of requested weeks:
    // This will fail when passing to Daylight Savings Time: $date = $first_week_start_date + (($week-1) * 604800);
   
$date = mktime( 0, 0, 0, 1, $days_to_new_week + 1 + ($week-1) * 7, $year );
   
// echo '<br> week '.$week.' starts on '.date( 'Y-m-d H:i:s', $date );

   
return $date;
}



/**
 * Get start and end day of a week, based on day f the week and start-of-week
 *
 * Used by Calendar
 *
 * @param date
 * @param integer 0 for Sunday, 1 for Monday
 */
function get_weekstartend( $date, $startOfWeek )
{
    while(
date('w', $date) <> $startOfWeek )
    {
       
// echo '<br />'.date('Y-m-d H:i:s w', $date).' - '.$startOfWeek;
        // mktime is needed so calculations work for DST enabling. Example: March 30th 2008, start of week 0 sunday
       
$date = mktime( 0, 0, 0, date('m',$date), date('d',$date)-1, date('Y',$date) );
    }
   
// echo '<br />'.date('Y-m-d H:i:s w', $date).' = '.$startOfWeek;
   
$week['start'] = $date;
   
$week['end']   = $date + 604800; // 7 days

    // pre_dump( 'weekstartend: ', date( 'Y-m-d', $week['start'] ), date( 'Y-m-d', $week['end'] ) );

   
return( $week );
}


/**
 * Get datetime rounded to lower minute. This is meant to remove seconds and
 * leverage MySQL's query cache by having SELECT queries remain identical for 60 seconds instead of just 1.
 *
 * @param integer UNIX timestamp
 * @param string Format (defaults to "Y-m-d H:i"). Use "U" for UNIX timestamp.
 */
function remove_seconds($timestamp, $format = 'Y-m-d H:i')
{
    return
date($format, floor($timestamp/60)*60);
}


/**
 * Convert from seconds to months, days, hours, minutes and seconds
 *
 * @param integer duration in seconds
 * @return array of [ years, months, days, hours, minutes, seconds ]
 */
function get_duration_fields( $duration )
{
   
$result = array();

   
$year_seconds = 31536000; // 1 year
   
$years = floor( $duration / $year_seconds );
   
$duration = $duration - $years * $year_seconds;
   
$result[ 'years' ] = $years;

   
$month_seconds = 2592000; // 1 month
   
$months = floor( $duration / $month_seconds );
   
$duration = $duration - $months * $month_seconds;
   
$result[ 'months' ] = $months;

   
$day_seconds = 86400; // 1 day
   
$days = floor( $duration / $day_seconds );
   
$duration = $duration - $days * $day_seconds;
   
$result[ 'days' ] = $days;

   
$hour_seconds = 3600; // 1 hour
   
$hours = floor( $duration / $hour_seconds );
   
$duration = $duration - $hours * $hour_seconds;
   
$result[ 'hours' ] = $hours;

   
$minute_seconds = 60; // 1 minute
   
$minutes = floor( $duration / $minute_seconds );
   
$duration = $duration - $minutes * $minute_seconds;
   
$result[ 'minutes' ] = $minutes;

   
$result[ 'seconds' ] = $duration;
    return
$result;
}


/**
 * Get a title of duration
 *
 * @param integer Duration in seconds
 * @param array Titles
 * @return string Duration title
 */
function get_duration_title( $duration, $titles = array() )
{
   
$titles = array_merge( array(
       
'year'   => T_('Last %d years'),
       
'month'  => T_('Last %d months'),
       
'day'    => T_('Last %d days'),
       
'hour'   => T_('Last %d hours'),
       
'minute' => T_('Last %d minutes'),
       
'second' => T_('Last %d seconds'),
        ),
$titles );

   
$delay_fields = get_duration_fields( $duration );

    if( ! empty(
$delay_fields[ 'years' ] ) )
    {
// Years
       
return sprintf( $titles['year'], $delay_fields[ 'years' ] );
    }
    elseif( ! empty(
$delay_fields[ 'months' ] ) )
    {
// Months
       
return sprintf( $titles['month'], $delay_fields[ 'months' ] );
    }
    elseif( ! empty(
$delay_fields[ 'days' ] ) )
    {
// Days
       
return sprintf( $titles['day'], $delay_fields[ 'days' ] );
    }
    elseif( ! empty(
$delay_fields[ 'hours' ] ) )
    {
// Hours
       
return sprintf( $titles['hour'], $delay_fields[ 'hours' ] );
    }
    elseif( ! empty(
$delay_fields[ 'minutes' ] ) )
    {
// Minutes
       
return sprintf( $titles['minute'], $delay_fields[ 'minutes' ] );
    }
    else
    {
// Seconds
       
return sprintf( $titles['second'], $delay_fields[ 'seconds' ] );
    }
}

function
get_lastseen_date( $date, $view = 'exact_date', $cheat = 0 )
{
   
$result = mysql2localedate( $date );

    if(
$view == 'blurred_date' )
    {
       
$result = (int)( ( ( time() - strtotime( $date ) ) / 86400 ) - $cheat);

        if(
$result < 0 )
        {
           
$result = 0;
        }

        if(
$result < 3 )
        {
           
$result = T_('less than 3 days ago');
        }
        elseif(
$result < 7 )
        {
           
$result = T_('less than a week ago');
        }
        elseif(
$result < 30 )
        {
           
$result = T_('less than a month ago');
        }
        elseif(
$result < 90 )
        {
           
$result = T_('less than 3 months ago');
        }
        else
        {
           
$result = T_('more than 3 months ago');
        }
    }

    return
$result;
}


/**
 * Validate variable
 *
 * @param string param name
 * @param string validator function name
 * @param boolean true if variable value can't be empty
 * @param custom error message
 * @return boolean true if OK
 */
function param_validate( $variable, $validator, $required = false, $custom_msg = NULL )
{
   
/* Tblue> Note: is_callable() does not check whether a function is
     *        disabled (http://www.php.net/manual/en/function.is-callable.php#79151).
     */
   
if( ! is_callable( $validator ) )
    {
       
debug_die( 'Validator function '.$validator.'() is not callable!' );
    }

    if( ! isset(
$GLOBALS[$variable] ) )
    {    
// Variable not set, we cannot handle this using the validator function...
       
if( $required )
        {    
// Add error:
           
param_check_not_empty( $variable, $custom_msg );
            return
false;
        }

        return
true;
    }

    if(
$GLOBALS[$variable] === '' && ! $required )
    {    
// Variable is empty or not set. That's fine since it isn't required:
       
return true;
    }

   
$msg = $validator( $GLOBALS[$variable] );

    if( !empty(
$msg ) )
    {
        if( !empty(
$custom_msg ) )
        {
           
$msg = $custom_msg;
        }

       
param_error( $variable, $msg );
        return
false;
    }

    return
true;
}


/**
 * Checks if the param is a decimal number
 *
 * @param string decimal to check
 * @return boolean true if OK
 */
function is_decimal( $decimal )
{
    return
preg_match( '#^[0-9]*(\.[0-9]+)?$#', $decimal );
}


/**
 * Checks if the param is an integer (no float, e.g. 3.14).
 *
 * @param string number to check
 * @return boolean true if OK
 */
function is_number( $number )
{
    return
preg_match( '#^[0-9]+$#', $number );
}


/**
 * Check that email address looks valid.
 *
 * @param string email address to check
 * @param string Format to use ('simple', 'rfc')
 *    'simple':
 *      Single email address.
 *    'rfc':
 *      Full email address, may include name (RFC2822)
 *      - example@example.org
 *      - Me <example@example.org>
 *      - "Me" <example@example.org>
 * @param boolean Return the match or boolean
 *
 * @return bool|array Either true/false or the match (see {@link $return_match})
 */
function is_email( $email, $format = 'simple', $return_match = false )
{
   
#$chars = "/^([a-z0-9_]|\\-|\\.)+@(([a-z0-9_]|\\-)+\\.)+[a-z]{2,4}\$/i";

   
switch( $format )
    {
        case
'rfc':
        case
'rfc2822':
           
/**
             * Regexp pattern converted from: http://www.regexlib.com/REDetails.aspx?regexp_id=711
             * Extended to allow escaped quotes.
             */
           
$pattern_email = '/^
                (
                    (?>[a-zA-Z\d!\#$%&\'*+\-\/=?^_`{|}~]+\x20*
                        |"( \\\" | (?=[\x01-\x7f])[^"\\\] | \\[\x01-\x7f] )*"\x20*)* # Name
                    (<)
                )?
                (
                    (?!\.)(?>\.?[a-zA-Z\d!\#$%&\'*+\-\/=?^_`{|}~]+)+
                    |"( \\\" | (?=[\x01-\x7f])[^"\\\] | \\[\x01-\x7f] )* " # quoted mailbox name
                )
                @
                (
                    ((?!-)[a-zA-Z\d\-]+(?<!-)\.)+[a-zA-Z]{2,}
                    |
                    \[(
                        ( (?(?<!\[)\.)(25[0-5] | 2[0-4]\d | [01]?\d?\d) ){4}
                        |
                        [a-zA-Z\d\-]*[a-zA-Z\d]:( (?=[\x01-\x7f])[^\\\[\]] | \\[\x01-\x7f] )+
                    )\]
                )
                (?(3)>) # match ">" if it was there
                $/x'
;
            break;

        case
'simple':
        default:
           
// '/^\S+@[^\.\s]\S*\.[a-z]{2,}$/i'
           
$pattern_email = '~^(([_a-z0-9-]+)(\.[_a-z0-9-]+)*@([a-z0-9-]+)(\.[a-z0-9-]+)*(\.[a-z]{2,}))$~i';
            break;
    }

    if(
strpos( $email, '@' ) !== false && strpos( $email, '.' ) !== false )
    {
        if(
$return_match )
        {
           
preg_match( $pattern_email, $email, $match );
            return
$match;
        }
        else
        {
            return (bool)
preg_match( $pattern_email, $email );
        }
    }
    else
    {
        return
$return_match ? array() : false;
    }
}


/**
 * Checks if the phone number is valid
 *
 * @param string phone number to check
 * @return boolean true if OK
 */
function is_phone( $phone )
{
    return
preg_match( '|^\+?[\-*#/(). 0-9]+$|', $phone );
}


/**
 * Checks if the url is valid
 *
 * @param string url to check
 * @return boolean true if OK
 */
function is_url( $url )
{
    if(
validate_url( $url, 'posting', false ) )
    {
        return
false;
    }

    return
true;
}


/**
 * Checks if the word is valid
 *
 * @param string word to check
 * @return boolean true if OK
 */
function is_word( $word )
{
    return
preg_match( '#^[A-Za-z]+$#', $word );
}


/**
 * Check if the login is valid (in terms of allowed chars)
 *
 * @param string login
 * @return boolean|string TRUE if OK, FALSE if error, special error cases: 'usr', 'long'
 */
function is_valid_login( $login, $force_strict_logins = false )
{
    global
$Settings;

   
$strict_logins = isset( $Settings ) ? $Settings->get('strict_logins') : 1;

   
// NOTE: in some places usernames are typed in by other users (messaging) or admins.
    // Having cryptic logins with hard to type letters is a PITA.

    // Step 1
    // Forbid the following characters in logins
   
if( preg_match( '~[\'"><@&\s]~', $login ) )
    {    
// WARNING: allowing ' or " or > or < will open security issues!
        // NOTE: allowing @ will make some "average" users use their email address (not good for their spam health)
        // NOTE: we do not allow whitespace in logins
       
return false;
    }

   
// Step 2
   
if( ($strict_logins || $force_strict_logins) && ! preg_match( '~^[A-Za-z0-9_.\-]+$~', $login ) )
    {    
// WARNING: allowing special chars like latin 1 accented chars ( \xDF-\xF6\xF8-\xFF ) will create issues with
        // user media directory names (tested on Max OS X) -- Do no allow any of this until we have a clean & safe media dir name generator.

        // fp> TODO: check why a dash '-' prevents renaming the fileroot
       
return false;
    }
    elseif( !
$strict_logins )
    {    
// We allow any character that is not explicitly forbidden in Step 1
        // Enforce additional limitations
       
$login = preg_replace( '|%([a-fA-F0-9][a-fA-F0-9])|', '', $login ); // Kill octets
       
$login = preg_replace( '/&.+?;/', '', $login ); // Kill entities
   
}

   
// Step 3
    // Special case, the login is valid however we forbid it's usage.
    // param_check_valid_login() will display a special error message in this case.
   
if( preg_match( '~^usr_~', $login ) )
    {    
// Logins cannot start with 'usr_', this prefix is reserved for system use
        // We create user media directories for users with non-ASCII logins in format /media/users/usr_55/, where 55 is user ID
       
return 'usr';
    }

   
// Step 4
    // To avoid MySQL erro on insert long data
   
if( utf8_strlen( $login ) > 20 )
    {    
// Don't allow long login:
       
return 'long';
    }

    return
true;
}


/**
 * Checks if the color is valid
 * Allowed formats: #09ABEF, rgba(100,200,255,0.99)
 *
 * @param string color to check
 * @return boolean true if OK
 */
function is_color( $color )
{
    return
preg_match( '~^(#([a-f0-9]{3}){1,2}|rgba\(\d{1,3},\d{1,3},\d{1,3},(1|0(\.\d{1,2})?)\))$~i', $color );
}


/**
 * Check if the login is valid (user exists)
 *
 * @param string login
 * @return boolean true if OK
 */
function user_exists( $login )
{
    global
$DB;

   
$SQL = new SQL( 'Get a count of users with login '.$login );
   
$SQL->SELECT( 'COUNT(*)' );
   
$SQL->FROM( 'T_users' );
   
$SQL->WHERE( 'user_login = '.$DB->quote( $login ) );

   
$var = $DB->get_var( $SQL );
    return
$var > 0 ? true : false; // PHP4 compatibility
}


/**
 * Are we running on a Windows server?
 */
function is_windows()
{
    return (
strtoupper(substr(PHP_OS,0,3)) == 'WIN' );
}


/**
 * Get all "a" tags from the given content
 *
 * @param string content
 * @return array all <a../a> part from the given content
 */
function get_atags( $content )
{
   
$tags = array();

    if(
preg_match_all( '#(<a[^>]+>(.*?)</a>|&lt;a.+&gt;(.*?)&lt;/a&gt;)#i', $content, $result ) )
    {
       
$tags = $result[0];
    }

    return
$tags;
}


/**
 * Add class to an html tag
 *
 * @param string HTML content
 * @param string Class to add to the tag in the HTML
 * @param integer Number of tags to add the class to
 * @return string HTML content with the added class
 */
function add_tag_class( $content, $class, $limit = 1 )
{
    global
$add_class;
   
$add_class = $class;

   
$content = preg_replace_callback( '/<[^\/].*?>/i', 'callback_add_tag_class',
       
$content, $limit );

    unset(
$add_class );

    return
$content;
}
function
callback_add_tag_class( $matches )
{
    global
$add_class;
   
// Check if class attribute is already defined
   
if( preg_match( '/\sclass\s*=/i', $matches[0] ) )
    {
// Insert class
        //$content = preg_replace( '/(<.*)(class\s*=\s*")(.*)"/i', '$1$2$3 '.$class.'"', $content, $limit );
       
return preg_replace( '/(<[a-zA-z_:]\s*?.*?\s*?class=")(.*?)(".*?>)/i', '$1$2 '.$add_class.'$3', $matches[0] );
    }
    else
    {
        return
preg_replace( '/>/i', ' class="'.$add_class.'"$1>', $matches[0] );
    }
}


/**
 * Get all "img" tags from the given content
 *
 * @param string content
 * @return array all <img../img> part from the given content
 */
function get_imgtags( $content )
{
   
$tag = 'img';
   
$regexp = '{<'.$tag.'[^>]*[ (</'.$tag.'>) | (/>) ]}';

   
preg_match_all( $regexp, $content, $result );
    return
$result[0];
}


/**
 * Get all urls from the given content
 *
 * @param string content
 * @return array all url from content
 */
function get_urls( $content )
{
   
$regexp = '^(?#Protocol)(?:(?:ht|f)tp(?:s?)\:\/\/|~\/|\/)?(?#Username:Password)(?:\w+:\w+@)?(?#Subdomains)(?:(?:[-\w]+\.)+(?#TopLevel Domains)(?:com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum|travel|[a-z]{2,4}))(?#Port)(?::[\d]{1,5})?(?#Directories)(?:(?:(?:\/(?:[-\w~!$+|.,;=]|%[a-f\d]{2})+)+|\/)+|\?|#)?(?#Query)(?:(?:\?(?:[-\w~!$+|.,;*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,;*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,;*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,;*:=]|%[a-f\d]{2})*)*)*(?#Anchor)(?:#(?:[-\w~!$+|.,;*:=]|%[a-f\d]{2})*)?^';

   
preg_match_all( $regexp, $content, $result );
    return
$result[0];
}


function
xmlrpc_getposttitle($content)
{
    global
$post_default_title;
    if (
preg_match('/<title>(.+?)<\/title>/is', $content, $matchtitle))
    {
       
$post_title = $matchtitle[1];
    }
    else
    {
       
$post_title = $post_default_title;
    }
    return(
$post_title);
}


/**
 * Also used by post by mail
 *
 * @deprecated by xmlrpc_getpostcategories()
 */
function xmlrpc_getpostcategory($content)
{
    if (
preg_match('/<category>([0-9]+?)<\/category>/is', $content, $matchcat))
    {
        return
$matchcat[1];
    }

    return
false;
}


/**
 * Extract categories out of "<category>" tag from $content.
 *
 * NOTE: w.bloggar sends something like "<category>00000013,00000001,00000004,</category>" to
 *       blogger.newPost.
 *
 * @return false|array
 */
function xmlrpc_getpostcategories($content)
{
   
$cats = array();

    if(
preg_match('~<category>(\d+\s*(,\s*\d*)*)</category>~i', $content, $match) )
    {
       
$cats = preg_split('~\s*,\s*~', $match[1], -1, PREG_SPLIT_NO_EMPTY);
        foreach(
$cats as $k => $v )
        {
           
$cats[$k] = (int)$v;
        }
    }

    return
$cats;
}


/*
 * xmlrpc_removepostdata(-)
 */
function xmlrpc_removepostdata($content)
{
   
$content = preg_replace('/<title>(.*?)<\/title>/si', '', $content);
   
$content = preg_replace('/<category>(.*?)<\/category>/si', '', $content);
   
$content = trim($content);
    return(
$content);
}


/**
 * Echo the XML-RPC call Result and optionally log into file
 *
 * @param object XMLRPC response object
 * @param boolean true to echo
 * @param mixed File resource or == '' for no file logging.
 */
function xmlrpc_displayresult( $result, $display = true, $log = '' )
{
    if( !
$result )
    {
// We got no response:
       
if( $display ) echo T_('No response!')."<br />\n";
        return
false;
    }

    if(
$result->faultCode() )
    {
// We got a remote error:
       
if( $display ) echo T_('Remote error'), ': ', $result->faultString(), ' (', $result->faultCode(), ")<br />\n";
       
debug_fwrite($log, $result->faultCode().' -- '.$result->faultString());
        return
false;
    }

   
// We'll display the response:
   
$val = $result->value();
   
$value = xmlrpc_decode_recurse($result->value());

    if(
is_array($value) )
    {
       
$out = '';
        foreach(
$value as $l_value)
        {
            if(
is_array( $l_value ) )
            {
               
$out .= ' [';
                foreach(
$l_value as $lv_key => $lv_val )
                {
                   
$out .= $lv_key.' => '.( is_array( $lv_val ) ? '{'.implode( '; ', $lv_val ).'}' : $lv_val ).'; ';
                }
               
$out .= '] ';
            }
            else
            {
               
$out .= ' ['.$l_value.'] ';
            }
        }
    }
    else
    {
       
$out = $value;
    }

   
debug_fwrite($log, $out);

    if(
$display ) echo T_('Response').': '.$out."<br />\n";

    return
$value;
}


/**
 * Log the XML-RPC call Result into LOG object
 *
 * @param object XMLRPC response object
 * @param Log object to add messages to
 * @return boolean true = success, false = error
 */
function xmlrpc_logresult( $result, & $message_Log, $log_payload = true )
{
    if( !
$result )
    {
// We got no response:
       
$message_Log->add( T_('No response!'), 'error' );
        return
false;
    }

    if(
$result->faultCode() )
    {
// We got a remote error:
       
$message_Log->add( T_('Remote error').': '.$result->faultString().' ('.$result->faultCode().')', 'error' );
        return
false;
    }

    if(
$log_payload )
    {
       
// We got a response:
       
$value = xmlrpc_decode_recurse($result->value());

        if(
is_array($value) )
        {
           
$out = '';
            foreach(
$value as $l_value)
            {
               
$out .= ' ['.var_export($l_value, true).'] ';
            }
        }
        else
        {
           
$out = $value;
        }

       
$message_Log->add( T_('Response').': '.$out, 'success' );
    }

    return
true;
}



function
debug_fopen($filename, $mode) {
    global
$debug;
    if (
$debug == 1 && ( !empty($filename) ) )
    {
       
$fp = fopen($filename, $mode);
        return
$fp;
    } else {
        return
false;
    }
}

function
debug_fwrite($fp, $string)
{
    global
$debug;
    if(
$debug && $fp )
    {
       
fwrite($fp, $string);
    }
}

function
debug_fclose($fp)
{
    global
$debug;
    if(
$debug && $fp )
    {
       
fclose($fp);
    }
}



/**
 * Wrap pre tag around {@link var_dump()} for better debugging.
 *
 * @param $var__var__var__var__,... mixed variable(s) to dump
 * @return true
 */
function pre_dump( $var__var__var__var__ )
{
    global
$is_cli;

   
#echo 'pre_dump(): '.debug_get_backtrace(); // see where a pre_dump() comes from

   
$func_num_args = func_num_args();
   
$count = 0;

    if( ! empty(
$is_cli) )
    {
// CLI, no encoding of special chars:
       
$count = 0;
        foreach(
func_get_args() as $lvar )
        {
           
var_dump($lvar);

           
$count++;
            if(
$count < $func_num_args )
            {
// Put newline between arguments
               
echo "\n";
            }
        }
    }
    elseif(
function_exists('xdebug_var_dump') )
    {
// xdebug already does fancy displaying:

        // no limits:
       
$old_var_display_max_children = @ini_set('xdebug.var_display_max_children', -1); // default: 128
       
$old_var_display_max_data = @ini_set('xdebug.var_display_max_data', -1); // max string length; default: 512
       
$old_var_display_max_depth = @ini_set('xdebug.var_display_max_depth', -1); // default: 3

       
echo "\n<div style=\"padding:1ex;border:1px solid #00f;\">\n";
        foreach(
func_get_args() as $lvar )
        {
           
xdebug_var_dump($lvar);

           
$count++;
            if(
$count < $func_num_args )
            {
// Put HR between arguments
               
echo "<hr />\n";
            }
        }
        echo
'</div>';

       
// restore xdebug settings:
       
@ini_set('xdebug.var_display_max_children', $old_var_display_max_children);
        @
ini_set('xdebug.var_display_max_data', $old_var_display_max_data);
        @
ini_set('xdebug.var_display_max_depth', $old_var_display_max_depth);
    }
    else
    {
       
$orig_html_errors = @ini_set('html_errors', 0); // e.g. xdebug would use fancy html, if this is on; we catch (and use) xdebug explicitly above, but just in case

       
echo "\n<pre style=\"padding:1ex;border:1px solid #00f;overflow:auto\">\n";
        foreach(
func_get_args() as $lvar )
        {
           
ob_start();
           
var_dump($lvar); // includes "\n"; do not use var_export() because it does not detect recursion by design
           
$buffer = ob_get_contents();
           
ob_end_clean();
            echo
htmlspecialchars($buffer);

           
$count++;
            if(
$count < $func_num_args )
            {
// Put HR between arguments
               
echo "<hr />\n";
            }
        }
        echo
"</pre>\n";
        @
ini_set('html_errors', $orig_html_errors);
    }
   
evo_flush();
    return
true;
}


/**
 * Get a function trace from {@link debug_backtrace()} as html table.
 *
 * Adopted from {@link http://us2.php.net/manual/de/function.debug-backtrace.php#47644}.
 *
 * @todo dh> Add support for $is_cli = true (e.g. in case of MySQL error)
 *
 * @param integer|NULL Get the last x entries from the stack (after $ignore_from is applied). Anything non-numeric means "all".
 * @param array After a key/value pair matches a stack entry, this and the rest is ignored.
 *              For example, array('class' => 'DB') would exclude everything after the stack
 *              "enters" class DB and everything that got called afterwards.
 *              You can also give an array of arrays which means that every condition in one of the given array must match.
 * @param integer Number of stack entries to include, after $ignore_from matches.
 * @return string HTML table
 */
function debug_get_backtrace( $limit_to_last = NULL, $ignore_from = array( 'function' => 'debug_get_backtrace' ), $offset_ignore_from = 0 )
{
    if( !
function_exists( 'debug_backtrace' ) ) // PHP 4.3.0
   
{
        return
'Function debug_backtrace() is not available!';
    }

   
$r = '';

   
$backtrace = debug_backtrace();
   
$count_ignored = 0; // remember how many have been ignored
   
$limited = false;   // remember if we have limited to $limit_to_last

   
if( $ignore_from )
    {    
// we want to ignore from a certain point
       
$trace_length = 0;
       
$break_because_of_offset = false;

        for(
$i = count($backtrace); $i > 0; $i-- )
        {    
// Search the backtrace from behind (first call).
           
$l_stack = & $backtrace[$i-1];

            if(
$break_because_of_offset && $offset_ignore_from < 1 )
            {
// we've respected the offset, but need to break now
               
break; // ignore from here
           
}

            foreach(
$ignore_from as $l_ignore_key => $l_ignore_value )
            {    
// Check if we want to ignore from here
               
if( is_array($l_ignore_value) )
                {    
// It's an array - all must match
                   
foreach( $l_ignore_value as $l_ignore_mult_key => $l_ignore_mult_val )
                    {
                        if( !isset(
$l_stack[$l_ignore_mult_key]) /* not set with this stack entry */
                           
|| strcasecmp($l_stack[$l_ignore_mult_key], $l_ignore_mult_val) /* not this value (case-insensitive) */ )
                        {
                            continue
2; // next ignore setting, because not all match.
                       
}
                    }
                    if(
$offset_ignore_from-- > 0 )
                    {
                       
$break_because_of_offset = true;
                        break;
                    }
                    break
2; // ignore from here
               
}
                elseif( isset(
$l_stack[$l_ignore_key])
                    && !
strcasecmp($l_stack[$l_ignore_key], $l_ignore_value) /* is equal case-insensitive */ )
                {
                    if(
$offset_ignore_from-- > 0 )
                    {
                       
$break_because_of_offset = true;
                        break;
                    }
                    break
2; // ignore from here
               
}
            }
           
$trace_length++;
        }

       
$count_ignored = count($backtrace) - $trace_length;

       
$backtrace = array_slice( $backtrace, 0-$trace_length ); // cut off ignored ones
   
}

   
$count_backtrace = count($backtrace);
    if(
is_numeric($limit_to_last) && $limit_to_last < $count_backtrace )
    {    
// we want to limit to a maximum number
       
$limited = true;
       
$backtrace = array_slice( $backtrace, 0, $limit_to_last );
       
$count_backtrace = $limit_to_last;
    }

   
$r .= '<div style="padding:1ex; margin-bottom:1ex; text-align:left; color:#000; background-color:#ddf;">
                    <h3>Backtrace:</h3>'
."\n";
    if(
$count_backtrace )
    {
       
$r .= '<ol style="font-family:monospace;">';

       
$i = 0;
        foreach(
$backtrace as $l_trace )
        {
            if( ++
$i == $count_backtrace )
            {
               
$r .= '<li style="padding:0.5ex 0;">';
            }
            else
            {
               
$r .= '<li style="padding:0.5ex 0; border-bottom:1px solid #77d;">';
            }
           
$args = array();
            if( isset(
$l_trace['args']) && is_array( $l_trace['args'] ) )
            {    
// Prepare args:
               
foreach( $l_trace['args'] as $l_arg )
                {
                   
$l_arg_type = gettype($l_arg);
                    switch(
$l_arg_type )
                    {
                        case
'integer':
                        case
'double':
                           
$args[] = $l_arg;
                            break;
                        case
'string':
                           
$args[] = '"'.strmaxlen(str_replace("\n", '\n', $l_arg), 255, NULL, 'htmlspecialchars').'"';
                            break;
                        case
'array':
                           
$args[] = 'Array('.count($l_arg).')';
                            break;
                        case
'object':
                           
$args[] = 'Object('.get_class($l_arg).')';
                            break;
                        case
'resource':
                           
$args[] = htmlspecialchars((string)$l_arg);
                            break;
                        case
'boolean':
                           
$args[] = $l_arg ? 'true' : 'false';
                            break;
                        default:
                           
$args[] = $l_arg_type;
                    }
                }
            }

           
$call = "<strong>\n";
            if( isset(
$l_trace['class']) )
            {
               
$call .= htmlspecialchars($l_trace['class']);
            }
            if( isset(
$l_trace['type']) )
            {
               
$call .= htmlspecialchars($l_trace['type']);
            }
           
$call .= htmlspecialchars($l_trace['function'])."( </strong>\n";
            if(
$args )
            {
               
$call .= ' '.implode( ', ', $args ).' ';
            }
           
$call .='<strong>)</strong>';

           
$r .= $call."<br />\n";

           
$r .= '<strong>';
            if( isset(
$l_trace['file']) )
            {
               
$r .= "File: </strong> ".$l_trace['file'];
            }
            else
            {
               
$r .= '[runtime created function]</strong>';
            }
            if( isset(
$l_trace['line']) )
            {
               
$r .= ' on line '.$l_trace['line'];
            }

           
$r .= "</li>\n";
        }
       
$r .= '</ol>';
    }
    else
    {
       
$r .= '<p>No backtrace available.</p>';
    }

   
// Extra notes, might be to much, but explains why we stopped at some point. Feel free to comment it out or remove it.
   
$notes = array();
    if(
$count_ignored )
    {
       
$notes[] = 'Ignored last: '.$count_ignored;
    }
    if(
$limited )
    {
       
$notes[] = 'Limited to'.( $count_ignored ? ' remaining' : '' ).': '.$limit_to_last;
    }
    if(
$notes )
    {
       
$r .= '<p class="small">'.implode( ' - ', $notes ).'</p>';
    }

   
$r .= "</div>\n";

    return
$r;
}


/**
 * Outputs Unexpected Error message. When in debug mode it also prints a backtrace.
 *
 * This should be used instead of die() everywhere.
 * This should NOT be used instead of exit() anywhere.
 * Dying means the application has encontered an unexpected situation,
 * i-e: something that should never occur during normal operation.
 * Examples: database broken, user changed URL by hand...
 *
 * @param string Message to output
 * @param array Additional params
 *        - "status" (Default: '500 Internal Server Error')
 *        - "debug_info" - Use this info instead of $additional_info when debug is ON
 */
function debug_die( $additional_info = '', $params = array() )
{
    global
$debug, $baseurl;
    global
$log_app_errors, $app_name, $is_cli, $display_errors_on_production, $is_api_request, $is_cron_job_executing;

   
$params = array_merge( array(
       
'status'     => '500 Internal Server Error',
       
'debug_info' => '',
        ),
$params );

    if(
$debug && ! empty( $params['debug_info'] ) )
    {
// Display 'debug_info' when debug is ON
       
$additional_info = $params['debug_info'];
    }

   
// Send the predefined cookies:
   
evo_sendcookies();

    if(
$is_api_request )
    {    
// REST API or XMLRPC request:

        // Set JSON content type:
       
headers_content_mightcache( 'application/json', 0, '#', false ); // Do NOT cache error messages! (Users would not see they fixed them)
       
header_http_response( $params['status'] );

        echo
json_encode( array(
               
'error_status' => $params['status'],
               
'error_info'   => $additional_info,
            ) );

        die(
1); // Error code 1. Note: This will still call the shutdown function.
   
}
    elseif(
$is_cron_job_executing )
    {    
// If debug die has been called during cron job executing:
       
$cron_job_error = '<div style="background-color: #ddd; padding: 1ex; margin-bottom: 1ex;">'
               
.'<h3>Additional information about this error:</h3>'
               
.$additional_info
           
.'</div>'
           
.debug_get_backtrace();
        throw new
Exception( str_replace( "\n", '', $cron_job_error ) );
    }
    elseif(
$is_cli )
    {
// Command line interface, e.g. in cron_exec.php:
       
echo '== '.T_('An unexpected error has occurred!')." ==\n";
        echo
T_('If this error persists, please report it to the administrator.')."\n";
        if(
$debug || $display_errors_on_production )
        {
// Display additional info only in debug mode or when it was explicitly set by display_errors_on_production setting because it can reveal system info to hackers and greatly facilitate exploits
           
echo T_('Additional information about this error:')."\n";
            echo
strip_tags( $additional_info )."\n\n";
        }
    }
    else
    {
       
// Attempt to output an error header (will not work if the output buffer has already flushed once):
        // This should help preventing indexing robots from indexing the error :P
       
if( ! headers_sent() )
        {
           
load_funcs('_core/_template.funcs.php');
           
headers_content_mightcache( 'text/html', 0, '#', false );  // Do NOT cache error messages! (Users would not see they fixed them)
           
$status_header = $_SERVER['SERVER_PROTOCOL'].' '.$params['status'];
           
header($status_header);
        }

       
$too_many_connections = false;
        if( ! empty(
$additional_info ) )
        {    
//Handling of "Too many connections":
           
$find_token = 'Too many connections';

            if(
preg_match( "/{$find_token}/i", $additional_info ) )
            {
               
load_funcs( 'skins/_skin.funcs.php' );
               
$too_many_connections = true;
                require
skin_fallback_path( 'too_many_connections.main.php', 6 );
               
http_response_code( 503 );
            }
        }

        if( !
$too_many_connections )
        {
            echo
'<div style="background-color: #fdd; padding: 1ex; margin-bottom: 1ex;">';
            echo
'<h3 style="color:#f00;">'.T_('An unexpected error has occurred!').'</h3>';
            echo
'<p>'.T_('If this error persists, please report it to the administrator.').'</p>';
            echo
'<p><a href="'.$baseurl.'">'.T_('Go back to home page').'</a></p>';
            echo
'</div>';

            if( ! empty(
$additional_info ) )
            {
                echo
'<div style="background-color: #ddd; padding: 1ex; margin-bottom: 1ex;">';
                if(
$debug || $display_errors_on_production )
                {
// Display additional info only in debug mode or when it was explicitly set by display_errors_on_production setting because it can reveal system info to hackers and greatly facilitate exploits
                   
echo '<h3>'.T_('Additional information about this error:').'</h3>';
                    echo
$additional_info;
                }
                else
                {
                    echo
'<p><i>Enable debugging to get additional information about this error.</i></p>' . get_manual_link('debugging','How to enable debug mode?');
                }
                echo
'</div>';

               
// Append the error text to AJAX log if it is AJAX request
               
ajax_log_add( $additional_info, 'error' );
               
ajax_log_display();
            }
        }
    }

    if(
$log_app_errors > 1 || $debug )
    {
// Prepare backtrace
       
$backtrace = debug_get_backtrace();

        if(
$log_app_errors > 1 || $is_cli )
        {
           
$backtrace_cli = trim(strip_tags($backtrace));
        }
    }

    if(
$log_app_errors )
    {
// Log error through PHP's logging facilities:
       
$log_message = $app_name.' error: ';
        if( ! empty(
$additional_info) )
        {
           
$log_message .= trim( strip_tags($additional_info) );
        }
        else
        {
           
$log_message .= 'No info specified in debug_die()';
        }

       
// Get file and line info:
       
$file = 'Unknown';
       
$line = 'Unknown';
        if(
function_exists('debug_backtrace') /* PHP 4.3 */ )
        {
// get the file and line
           
foreach( debug_backtrace() as $v )
            {
                if( isset(
$v['function']) && $v['function'] == 'debug_die' )
                {
                   
$file = isset($v['file']) ? $v['file'] : 'Unknown';
                   
$line = isset($v['line']) ? $v['line'] : 'Unknown';
                    break;
                }
            }
        }
       
$log_message .= ' in '.$file.' at line '.$line;

        if(
$log_app_errors > 1 )
        {
// Append backtrace:
            // indent after newlines:
           
$backtrace_cli = preg_replace( '~(\S)(\n)(\S)~', '$1  $2$3', $backtrace_cli );
           
$log_message .= "\nBacktrace:\n".$backtrace_cli;
        }
       
$log_message .= "\nREQUEST_URI:  ".( isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '-' );
       
$log_message .= "\nHTTP_REFERER: ".( isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '-' );

       
error_log( str_replace("\n", ' / ', $log_message), 0 /* PHP's system logger */ );
    }


   
// DEBUG OUTPUT:
   
if( $debug )
    {
        if(
$is_cli )
            echo
$backtrace_cli;
        else
            echo
$backtrace;
    }

   
// EXIT:
   
if( ! $is_cli )
    {
// Attempt to keep the html valid (but it doesn't really matter anyway)
       
echo '</body></html>';
    }

    die(
1);    // Error code 1. Note: This will still call the shutdown function.
}


/**
 * Outputs Bad request Error message. When in debug mode it also prints a backtrace.
 *
 * This should be used when a bad user input is detected.
 *
 * @param string Message to output (HTML/TEXT)
 * @param string Output format: 'html', 'text'
 */
function bad_request_die( $additional_info = '', $error_code = '400 Bad Request', $format = 'html' )
{
    global
$debug, $baseurl, $is_api_request;

   
// Send the predefined cookies:
   
evo_sendcookies();

    if(
$is_api_request )
    {    
// REST API or XMLRPC request:

        // Set JSON content type:
       
headers_content_mightcache( 'application/json', 0, '#', false ); // Do NOT cache error messages! (Users would not see they fixed them)
       
header_http_response( $error_code );

        echo
json_encode( array(
               
'error_status' => $error_code,
               
'error_info'   => $additional_info,
            ) );

        die(
2); // Error code 2. Note: this will still call the shutdown function.
   
}

   
// Attempt to output an error header (will not work if the output buffer has already flushed once):
    // This should help preventing indexing robots from indexing the error :P
   
if( ! headers_sent() )
    {
       
load_funcs('_core/_template.funcs.php');
       
headers_content_mightcache( 'text/html', 0, '#', false );        // Do NOT cache error messages! (Users would not see they fixed them)
       
header_http_response( $error_code );
    }

    if(
$format == 'text' )
    {    
// Display error message in TEXT format:
       
echo $additional_info;
       
// Display AJAX Log if it was initialized:
       
ajax_log_display();
        die(
2); // Error code 2. Note: this will still call the shutdown function.
   
}
   
// else display error message in HTML format:

   
if( ! function_exists( 'T_' ) )
    {    
// Load locale funcs to initialize function "T_" because it is used below:
       
load_funcs( 'locales/_locale.funcs.php' );
    }

    echo
'<div style="background-color: #fdd; padding: 1ex; margin-bottom: 1ex;">';
    echo
'<h3 style="color:#f00;">'.T_('Bad Request!').'</h3>';
    echo
'<p>'.T_('The parameters of your request are invalid.').'</p>';
    echo
'<p>'.T_('If you have obtained this error by clicking on a link INSIDE of this site, please report the bad link to the administrator.').'</p>';
    echo
'<p><a href="'.$baseurl.'">'.T_('Go back to home page').'</a></p>';
    echo
'</div>';

    if( !empty(
$additional_info ) )
    {
        echo
'<div style="background-color: #ddd; padding: 1ex; margin-bottom: 1ex;">';
        if(
$debug )
        {    
// Display additional info only in debug mode because it can reveal system info to hackers and greatly facilitate exploits
           
echo '<h3>'.T_('Additional information about this error:').'</h3>';
            echo
$additional_info;
        }
        else
        {
            echo
'<p><i>Enable debugging to get additional information about this error.</i></p>' . get_manual_link('debugging','How to enable debug mode?');
        }
        echo
'</div>';

       
// Append the error text to AJAX log if it is AJAX request
       
ajax_log_add( $additional_info, 'error' );
    }

    if(
$debug )
    {
        echo
debug_get_backtrace();
    }

   
// Attempt to keep the html valid (but it doesn't really matter anyway)
   
echo '</body></html>';

   
// Display AJAX Log if it was initialized:
   
ajax_log_display();

    die(
2); // Error code 2. Note: this will still call the shutdown function.
}


/**
 * Outputs debug info, according to {@link $debug} or $force param. This gets called typically at the end of the page.
 *
 * @param boolean true to force output regardless of {@link $debug}
 * @param boolean true to force clean output (without HTML) regardless of {@link $is_cli}
 */
function debug_info( $force = false, $force_clean = false )
{
    global
$debug, $debug_done, $debug_jslog, $debug_jslog_done, $Debuglog, $DB, $obhandler_debug, $Timer, $ReqHost, $ReqPath, $is_cli;
    global
$cache_imgsize, $cache_File;
    global
$Session;
    global
$db_config, $tableprefix, $http_response_code, $disp, $disp_detail, $robots_index, $robots_follow, $content_type_header;
   
/**
     * @var Hit
     */
   
global $Hit;

   
// Detect content-type
   
$content_type = NULL;
    foreach(
headers_list() as $header)
    {
        if(
stripos($header, 'content-type:') !== false )
        {
// content type sent
            # "Content-Type:text/html;charset=utf-8" => "text/html"
           
$content_type = explode( ':', $header, 2 );
           
$content_type = explode( ';', array_pop( $content_type ) );
           
$content_type = trim( array_shift( $content_type ) );
            break;
        }
    }

   
// ---- Print AJAX Log
   
if( empty( $debug_jslog_done ) && ( $debug || $debug_jslog ) && $content_type == 'text/html' )
    {    
// Display debug jslog once
       
global $rsc_url, $app_version_long;

       
$relative_to = ( is_admin_page() ? 'rsc_url' : 'blog' );

       
require_js_defer( '#jqueryUI#', $relative_to, true );
       
require_css( '#jqueryUI_css#', $relative_to, NULL, NULL, '#', true );
       
require_js_defer( 'debug_jslog.js', $relative_to, true );
       
require_js_defer( 'ext:jquery/cookie/jquery.cookie.min.js', $relative_to, true );

       
$jslog_style_cookies = param_cookie( 'jslog_style', 'string' );
       
$jslog_styles = array();
        if( !empty(
$jslog_style_cookies ) )
        {    
// Get styles only from cookies
           
$jslog_style_cookies = explode( ';', $jslog_style_cookies );
            foreach(
$jslog_style_cookies as $jsc => $style )
            {
                if(
strpos( $style, 'height' ) !== false /*|| ( strpos( $style, 'display' ) !== false && !$debug_jslog )*/ )
                {    
// Unset the height param from defined styles ( and the display param if jslog is disabled )
                   
unset( $jslog_style_cookies[$jsc] );
                }
            }
           
$jslog_styles[] = implode( ';', $jslog_style_cookies );
        }
        else
        {
            if( !
is_logged_in() )
            {    
// Align top when evobar is hidden
               
$jslog_styles[] = 'top:0';
            }
            if(
$debug_jslog )
            {    
// Display the jslog
               
$jslog_styles[] = 'display:block';
            }
        }
       
$jslog_styles = count( $jslog_styles ) > 0 ? ' style="'.implode( ';', $jslog_styles ).'"' : '';

       
$close_url = url_add_param( $_SERVER['REQUEST_URI'], 'jslog' );
        echo
'<div id="debug_ajax_info" class="debug"'.$jslog_styles.'>';
        echo
'<div class="jslog_titlebar">'.
               
'AJAX Debug log'.get_manual_link('ajax_debug_log').
               
action_icon( T_('Close'), 'close', $close_url, NULL, NULL, NULL, array( 'class' => 'jslog_switcher' ) ).
           
'</div>';
        echo
'<div id="jslog_container"></div>';
        echo
'<div class="jslog_statusbar">'.
               
'<a href="'.$_SERVER['REQUEST_URI'].'#" class="jslog_clear">'.T_('Clear').'</a>'.
           
'</div>';
        echo
'</div>';

       
// Make sure debug jslog output only happens once:
       
$debug_jslog_done = true;
    }
   
// ----

    // clean output:
   
$clean = $is_cli || $force_clean;

    if( !
$force )
    {
        if( ! empty(
$debug_done ) )
        {
// Already displayed!
           
return;
        }

        if( empty(
$debug ) || // No debug output desired:
           
( $debug < 2 && $content_type != 'text/html' ) ) // Do not display, if no content-type header has been sent or it's != "text/html" (debug > 1 skips this)
       
{
            global
$evo_last_handled_error;
            if( ! empty(
$evo_last_handled_error ) )
            {
// If script has been stoppped by some error
                // Display a message when debug is OFF and error has occured
               
$debug_off_title = 'An unexpected error has occured!';
               
$debug_off_msg1 = 'We apologize for the inconvenience.';
               
$debug_off_msg2 = 'This error has been automatically reported and we will work to resolve it as fast as possible.';
                if(
$clean )
                {
// CLI mode
                   
echo '*** '.$debug_off_title.' ***'."\n\n"
                       
.$debug_off_msg1."\n"
                       
.$debug_off_msg2;
                }
                else
                {
// View from browser
                   
echo '<div style="margin:1em auto;padding:10px;background:#FEFFFF;border:2px solid #F00;border-radius:6px;text-align:center;">'
                           
.'<h2 style="margin:0;color:#F00;">'.$debug_off_title.'</h2>'
                           
.'<p>'.$debug_off_msg1.'</p>'
                           
.'<p style="margin-bottom:0">'.$debug_off_msg2.'</p>'
                       
.'</div>';
                }
            }
            return;
        }
    }
   
//Make sure debug output only happens once:
   
$debug_done = true;

   
$printf_format = '| %-45s | %-5s | %-7s | %-5s |';
   
$table_headerlen = 73;
   
/* This calculates the number of dashes to print e. g. on the top and
     * bottom of the table and after the header, making the table look
     * better (looks like the tables of the mysql command line client).
     * Normally, the value won't change, so it's hardcoded above. If you
     * change the printf() format above, this might be useful.
    preg_match_all( '#\d+#', $printf_format, $table_headerlen );
    $table_headerlen = array_sum( $table_headerlen[0] ) +
                                    strlen( preg_replace( '#[^ \|]+#', '',
                                                $printf_format ) ) - 2;
    */

   
$ReqHostPathQuery = $ReqHost.$ReqPath.( empty( $_SERVER['QUERY_STRING'] ) ? '' : '?'.$_SERVER['QUERY_STRING'] );

    echo
"\n\n\n";
    if( !
$clean )
    {
        echo
'<div class="debug" id="debug_info">';
    }

   
// FULL DEBUG INFO(s) FROM PREVIOUS SESSION(s), after REDIRECT(s):
   
$get_redirected_debuginfo_from_sess_ID = param( 'get_redirected_debuginfo_from_sess_ID', 'integer' );
    if( ! empty(
$get_redirected_debuginfo_from_sess_ID ) )
     {    
// Get Session by ID for debug info from redirected page:
        // (This is used for redirect from different domain)
       
$debug_info_Session = new Session( $get_redirected_debuginfo_from_sess_ID );
    }
    elseif( isset(
$Session ) )
    {    
// Use current Session for debug info from redirected page:
       
$debug_info_Session = $Session;
    }

    if( isset(
$debug_info_Session ) && ! empty( $debug_info_Session->ID ) )
    {    
// Get debug info from redirected page:
       
$sess_debug_infos = $debug_info_Session->get( 'debug_infos' );
    }

    if( ! empty(
$sess_debug_infos ) )
    {    
// Display debug info from redirected page:
       
$count_sess_debug_infos = count( $sess_debug_infos );
        if(
$count_sess_debug_infos > 1 )
        {    
// Links to those Debuglogs:
           
if( $clean )
            {    
// kind of useless, but anyway...
               
echo "\n".'There are '.$count_sess_debug_infos.' debug infos from redirected pages.'."\n";
            }
            else
            {
                echo
'<p>There are '.$count_sess_debug_infos.' debug infos from redirected pages: ';
                for(
$i = 1; $i <= $count_sess_debug_infos; $i++ )
                {
                    echo
'<a href="'.$ReqHostPathQuery.'#debug_sess_debug_info_'.$i.'">#'.$i.'</a> ';
                }
                echo
'</p>';
            }
        }

        foreach(
$sess_debug_infos as $k => $sess_debug_info )
        {
            if(
$clean )
            {
                echo
"\n".'== Debug messages from redirected page (#'.( $k + 1 ).') =='."\n"
                   
.'See below for the Debuglog from the current request.'."\n";
                echo
gzdecode( $sess_debug_info );
            }
            else
            {
                echo
'<div class="debug_session">';
                echo
'<h3 id="debug_sess_debug_info_'.( $k + 1 ).'" style="color:#f00">Debug messages from redirected page (#'.( $k + 1 ).')</h3>'
                   
// link to real Debuglog:
                   
.'<p><a href="'.$ReqHostPathQuery.'#debug_current">See below for the debug from the current request.</a></p>';
               
$sess_debug_info = gzdecode( $sess_debug_info );
               
// Fix all anchors to proper work with SESSION debug info and do NOT mix with current debug info where same achors are used but without number suffix ($k + 1):
               
$anchors_regexp = '(evo_debug_queries|debug_info_cat_[^"]+)';
               
$sess_debug_info = preg_replace( '/id="'.$anchors_regexp.'"/', 'id="$1_'.( $k + 1 ).'"', $sess_debug_info );
                echo
preg_replace( '/href="([^#"]*)#'.$anchors_regexp.'"/', 'href="'.$ReqHostPathQuery.'#$2_'.( $k + 1 ).'"', $sess_debug_info );
                echo
'</div>';
            }
        }

        if( !
$clean )
        {    
// Anchor to scrolldown to current debug:
           
echo '<a id="debug_current"></a>';
        }

       
// link to first sess_debug_infos:
       
if( $clean )
        {
            echo
'See above for the debug info(s) from before the redirect.'."\n";
        }
        else
        {
            echo
'<p><a href="'.$ReqHostPathQuery.'#debug_sess_debug_info_1">See above for the debug info(s) from before the redirect.</a></p>';
        }

       
// Delete logs since they have been displayed...
        // EXCEPT if we are redirecting, because in this case we won't see these logs in a browser (only in request debug tools)
        // So in that case we want them to move over to the next page...
       
if( $http_response_code < 300 || $http_response_code >= 400 )
        {    
// This is NOT a 3xx redirect, assume debuglogs have been seen & delete them:
           
$debug_info_Session->delete( 'debug_infos' );
        }

        echo
"\n\n\n";
    }
   
// END OF DEBUG FROM PREVIOUS SESSIONS, after REDIRECT(s).

   
$debug_info_title = empty( $count_sess_debug_infos ) ? 'Debug info' : 'Debug info from Current page';
    echo (
$clean ? '*** '.$debug_info_title.' ***'."\n\n" : '<h2>'.$debug_info_title.'</h2>' );

    if( !
$obhandler_debug )
    {
// don't display changing items when we want to test obhandler

        // ---------------------------

       
echo '<div class="log_container"><div>';

        echo
'HTTP Response code: '.$http_response_code;
        echo
$clean ? "\n" : '<br />';

        echo
'$content_type_header: '.$content_type_header;
        echo
$clean ? "\n" : '<br />';

        echo
'$disp: '.$disp.' -- detail: '.$disp_detail;
        echo
$clean ? "\n" : '<br />';

        echo
'$robots_index: '.$robots_index;
        echo
$clean ? "\n" : '<br />';

        echo
'$robots_follow: '.$robots_follow;
        echo
$clean ? "\n" : '<br />';

        echo
'</div></div>';
    }

    if( !
$obhandler_debug )
    {
// don't display changing items when we want to test obhandler

        // ================================== DB Summary ================================
       
if( isset($DB) )
        {
            echo
'<div class="log_container"><div>';
            echo
$DB->num_queries.' SQL queries executed in '.$Timer->get_duration( 'SQL QUERIES' )." seconds\n";
            if( !
$clean )
            {
                echo
' &nbsp; <a href="'.$ReqHostPathQuery.'#evo_debug_queries">scroll down to details</a><p>';
            }
            echo
'</div></div>';
        }

       
// ========================== Timer table ================================
       
$time_page = $Timer->get_duration( 'total' );
       
$timer_rows = array();
        foreach(
$Timer->get_categories() as $l_cat )
        {
            if(
$l_cat == 'sql_query' )
            {
                continue;
            }
           
$timer_rows[ $l_cat ] = $Timer->get_duration( $l_cat );
        }
       
// Don't sort to see orginal order of creation
        // arsort( $timer_rows );
        // ksort( $timer_rows );

        // Remove "total", it will get output as the last one:
       
$total_time = $timer_rows['total'];
        unset(
$timer_rows['total']);

       
$percent_total = $time_page > 0 ? number_format( 100/$time_page * $total_time, 2 ) : '0';

        if(
$clean )
        {
            echo
'== Timers =='."\n\n";
            echo
'+'.str_repeat( '-', $table_headerlen ).'+'."\n";
           
printf( $printf_format."\n", 'Category', 'Time', '%', 'Count' );
            echo
'+'.str_repeat( '-', $table_headerlen ).'+'."\n";
        }
        else
        {
            echo
'<table class="debug_timer"><thead>'
               
.'<tr><td colspan="4" class="center">Timers</td></tr>' // dh> TODO: should be TH. Workaround so that tablesorter does not pick it up. Feedback from author requested.
               
.'<tr><th>Category</th><th>Time</th><th>%</th><th>Count</th></tr>'
               
.'</thead>';

           
// Output "total":
           
echo "\n<tfoot><tr>"
               
.'<td>total</td>'
               
.'<td class="right red">'.$total_time.'</td>'
               
.'<td class="right">'.$percent_total.'%</td>'
               
.'<td class="right">'.$Timer->get_count('total').'</td></tr></tfoot>';

            echo
'<tbody>';
        }

       
$table_rows_collapse = array();
        foreach(
$timer_rows as $l_cat => $l_time )
        {
           
$percent_l_cat = $time_page > 0 ? number_format( 100/$time_page * $l_time, 2 ) : '0';

            if(
$clean )
            {
               
$row = sprintf( $printf_format, $l_cat, $l_time, $percent_l_cat.'%', $Timer->get_count( $l_cat ) );
            }
            else
            {
               
$row = "\n<tr>"
                   
.'<td>'.$l_cat.'</td>'
                   
.'<td class="right">'.$l_time.'</td>'
                   
.'<td class="right">'.$percent_l_cat.'%</td>'
                   
.'<td class="right">'.$Timer->get_count( $l_cat ).'</td></tr>';
            }

           
// Maybe ignore this row later, but not for clean display.
           
if( ! $clean && ( $percent_l_cat < 1  ) )
            {    
// Hide everything that tool less tahn 5% of the time
               
$table_rows_collapse[] = $row;
            }
            else
            {
                echo
$row."\n";
            }
        }
       
$count_collapse = count($table_rows_collapse);
       
// Collapse ignored rows, allowing to expand them with Javascript:
       
if( $count_collapse > 5 )
        {
            echo
'<tr><td colspan="4" class="center">';
            echo
'<a href="" onclick="jQuery(this).closest(\'tbody\').next().toggle(); return false;">+ '.$count_collapse.' queries &lt; 1%</a> </td></tr>';
            echo
'</tbody>';
            echo
'<tbody style="display:none">';
        }
        echo
implode( "\n", $table_rows_collapse )."\n";

        if (
$clean )
        {
// "total" (done in tfoot for html above)
           
echo sprintf( $printf_format, 'total', $total_time, $percent_total.'%', $Timer->get_count('total') );
            echo
'+'.str_repeat( '-', $table_headerlen ).'+'."\n\n";
        }
        else
        {
            echo
"\n</tbody></table>";

           
// add jquery.tablesorter to the "Debug info" table.
           
$relative_to = ( is_admin_page() ? 'rsc_url' : 'blog' );
           
require_js_defer( 'ext:jquery/tablesorter/jquery.tablesorter.min.js', $relative_to, true );
           
require_js_defer( 'src/evo_init_debug_timer.js', $relative_to, true );
        }


       
// ================================ Opcode caching ================================
       
echo '<div class="log_container"><div>';
        echo
'Opcode cache: '.get_active_opcode_cache();
        echo
$clean ? "\n" : '<p>';

       
// ================================ User caching ================================
       
echo 'User cache: '.get_active_user_cache();
        echo
$clean ? "\n" : '<p>';
        echo
'</div></div>';


       
// ================================ Memory Usage ================================
       
echo '<div class="log_container"><div>';

        foreach( array(
// note: 8MB is default for memory_limit and is reported as 8388608 bytes
           
'memory_get_usage' => array( 'display' => 'Memory usage', 'high' => 8000000 ),
           
'memory_get_peak_usage' => array( 'display' => 'Memory peak usage', 'high' => 8000000 ) ) as $l_func => $l_var )
        {
            if(
function_exists( $l_func ) )
            {
               
$_usage = $l_func();

                if(
$_usage > $l_var['high'] )
                {
                    echo
$clean ? '[!!] ' : '<span style="color:red; font-weight:bold">';
                }

                echo
$l_var['display'].': '.bytesreadable( $_usage, ! $clean );

                if( !
$clean && $_usage > $l_var['high'] )
                {
                    echo
'</span>';
                }
                echo
$clean ? "\n" : '<br />';
            }
        }

        echo
'Len of serialized $cache_imgsize: '.strlen(serialize($cache_imgsize));
        echo
$clean ? "\n" : '<br />';
        echo
'Len of serialized $cache_File: '.strlen(serialize($cache_File));
        echo
$clean ? "\n" : '<br />';

        echo
'</div></div>';
    }


   
// CURRENT DEBUGLOG (with list of categories at top):
   
$log_categories = array( 'error', 'note', 'all' ); // Categories to output (in that order)
   
$log_container_head = $clean ? ( "\n".'== Debug messages =='."\n" ) : '<h3 id="debug_debuglog">Debug messages</h3>';

    if ( !
$clean )
    {
       
$log_cats = array_keys($Debuglog->get_messages( $log_categories )); // the real list (with all replaced and only existing ones)
       
$log_head_links = array();
        foreach(
$log_cats as $l_cat )
        {
           
$log_head_links[] .= '<a href="'.$ReqHostPathQuery.'#debug_info_cat_'.str_replace( ' ', '_', $l_cat ).'">'.$l_cat.'</a>';
        }
       
$log_container_head .= implode( ' | ', $log_head_links );

        echo
format_to_output(
           
$Debuglog->display( array(
                   
'container' => array( 'string' => $log_container_head, 'template' => false ),
                   
'all' => array( 'string' => '<h4 id="debug_info_cat_%s">%s:</h4>', 'template' => false ) ),
               
'', false, $log_categories ),
           
'htmlbody' );

        echo
'<h3 id="evo_debug_queries">DB</h3>';
    }
    else
    {
        echo
format_to_output(
           
$Debuglog->display( array(
                   
'container' => array( 'string' => $log_container_head, 'template' => false ),
                   
'all' => array( 'string' => '= %s ='."\n\n", 'template' => false ) ),
               
'', false, $log_categories, '', 'raw', false ),
           
'raw' );

        echo
"\n".'== DB =='."\n\n";
    }

    if(
$db_config)
    {
        if ( !
$clean )
        {
            echo
'<pre>';
        }

        echo
'Config DB Username: '.$db_config['user']."\n".
           
'Config DB Database: '.$db_config['name']."\n".
             
'Config DB Host: '.(isset($db_config['host']) ? $db_config['host'] : 'unset (localhost)')."\n".
             
'Config DB tables prefix: '.$tableprefix."\n".
             
'Config DB connection charset: '.$db_config['connection_charset']."\n";

        echo
$clean ? "\n" : '</pre>';
    }

    if( !isset(
$DB) )
    {
        echo
'No DB object.'.( $clean ? "\n" : '' );
    }
    else
    {
        echo
'<pre>Current DB charset: '.$DB->get_connection_charset()."</pre>\n";

       
$DB->dump_queries( ! $clean );
    }

    if ( !
$clean )
    {
        echo
'</div>';
    }
}


/**
 * Exit when request is blocked
 *
 * @param string Block type: 'IP', 'Domain', 'Country'
 * @param string Log message
 * @param string Syslog origin type: 'core', 'plugin'
 * @param integer Syslog origin ID
 */
function exit_blocked_request( $block_type, $log_message, $syslog_origin_type = 'core', $syslog_origin_ID = NULL )
{
    global
$debug;

   
// Write system log for the request:
   
syslog_insert( $log_message, 'warning', NULL, NULL, $syslog_origin_type, $syslog_origin_ID );

   
// Print out this text to inform an user:
   
echo 'This request has been blocked.';

    if(
$debug )
    {
// Display additional info on debug mode:
       
echo ' ('.$block_type.')';
    }

   
// EXIT:
   
exit( 0 );
}


/**
 * Check if the current request exceed the post max size limit.
 * If too much data was sent add an error message and call header redirect.
 */
function check_post_max_size_exceeded()
{
    global
$Messages;

    if( (
$_SERVER['REQUEST_METHOD'] == 'POST' ) && empty( $_POST ) && empty( $_FILES ) && ( $_SERVER['CONTENT_LENGTH'] > 0 ) )
    {
       
// Check post max size ini setting
       
$post_max_size = ini_get( 'post_max_size' );

       
// Convert post_max_size value to bytes
       
switch ( substr( $post_max_size, -1 ) )
        {
            case
'G':
               
$post_max_size = $post_max_size * 1024;
            case
'M':
               
$post_max_size = $post_max_size * 1024;
            case
'K':
               
$post_max_size = $post_max_size * 1024;
        }

       
// Add error message and redirect back to the referer url
       
$Messages->add( sprintf( T_('You have sent too much data (too many large files?) for the server to process (%s sent / %s maximum). Please try again by sending less data/files at a time.'), bytesreadable( $_SERVER['CONTENT_LENGTH'] ), bytesreadable( $post_max_size ) ) );
       
header_redirect( $_SERVER['HTTP_REFERER'] );
        exit(
0); // Already exited here
   
}
}


/**
 * Prevent email header injection.
 */
function mail_sanitize_header_string( $header_str, $close_brace = false )
{
   
// Prevent injection! (remove everything after (and including) \n or \r)
   
$header_str = preg_replace( '~(\r|\n).*$~s', '', trim($header_str) );

    if(
$close_brace && strpos( $header_str, '<' ) !== false && strpos( $header_str, '>' ) === false )
    {
// We have probably stripped the '>' at the end!
       
$header_str .= '>';
    }

    return
$header_str;
}

/**
 * Encode to RFC 1342 "Representation of Non-ASCII Text in Internet Message Headers"
 *
 * @param string
 * @param string 'Q' for Quoted printable, 'B' for base64
 */
function mail_encode_header_string( $header_str, $mode = 'Q' )
{
    global
$evo_charset;

   
/* mbstring way  (did not work for Alex RU)
    if( function_exists('mb_encode_mimeheader') )
    { // encode subject
        $orig = mb_internal_encoding();
        mb_internal_encoding('utf-8');
        $r = mb_encode_mimeheader( $header_str, 'utf-8', $mode );
        mb_internal_encoding($orig);
        return $r;
    }
    */

   
if( preg_match( '~[^a-z0-9!*+\-/ ]~i', $header_str ) )
    {
// If the string actually needs some encoding
       
if( $mode == 'Q' )
        {
// Quoted printable is best for reading with old/text terminal mail reading/debugging stuff:
           
$header_str = preg_replace_callback( '#[^a-z0-9!*+\-/ ]#i', 'mail_encode_header_string_callback', $header_str );
           
$header_str = str_replace( ' ', '_', $header_str );
           
$header_str = '=?'.$evo_charset.'?Q?'.$header_str.'?=';
        }
        else
        {
// Base 64 -- Alex RU way:
           
$header_str = '=?'.$evo_charset.'?B?'.base64_encode( $header_str ).'?=';
        }
    }

    return
$header_str;
}


/**
 * Callback function for mail header encoding
 *
 * @param array Matches
 * @return string
 */
function mail_encode_header_string_callback( $matches )
{
    return
sprintf( '=%02x', ord( stripslashes( $matches[0] ) ) );
}


/**
 * Get setting's value from General or User's settings
 *
 * @param integer User ID
 * @param string Setting ( email | name )
 * @return string Setting's value
 */
function user_get_notification_sender( $user_ID, $setting )
{
    global
$Settings;

   
$setting_name = 'notification_sender_'.$setting;

    if( empty(
$user_ID ) )
    {    
// Get value from general settings
       
return $Settings->get( $setting_name );
    }

   
$UserCache = & get_UserCache();
    if(
$User = & $UserCache->get_by_ID( $user_ID ) )
    {
        if(
$User->check_status( 'is_validated' ) )
        {    
// User is Activated or Autoactivated or Manually activated
           
global $UserSettings;
            if(
$UserSettings->get( $setting_name, $user_ID ) == '' )
            {    
// The user's setting is not defined yet
                // Update the user's setting from general setting
               
$UserSettings->set( $setting_name, $Settings->get( $setting_name ), $user_ID );
               
$UserSettings->dbupdate();
            }
            else
            {    
// User has a defined setting; Use this
               
return $UserSettings->get( $setting_name, $user_ID );
            }
        }
    }

    return
$Settings->get( $setting_name );
}


/**
 * Get limit for email sending for currently executing cron job
 *
 * @return integer 0 means either unlimited email sending or no cron job is executing at the moment
 */
function get_cron_job_emails_limit()
{
    global
$executing_cron_task_key;

    if( isset(
$executing_cron_task_key ) )
    {    
// If any cron job is really executing now:
       
global $Settings;
        return
intval( $Settings->get( 'cjob_maxemail_'.$executing_cron_task_key ) );
    }

   
// No currently executing cron job:
   
return 0;
}


/**
 * Check if current executing cron job should be stopped because of email sending limit
 *
 * 1. THIS MUST BE CALLED ONLY FROM INSIDE THE MAIN LOOP of the cronjob (*.job.php), not from within a generic function.
 * 2. THE GOAL is to NOT overload the sending  mail server by creating a PAUSE afer (approximately) the pax number of email to send in a chunk
 * 2B. The goal is that the next execution of the cron job will continue sending emails where the current job has left off.
 * 2C. The goal is NOT to miss sending notifications by generating send errors!
 * 3. Counter increase must be done SEPARATELY when an email is sent.
 *
 * @return boolean TRUE if cron job execution can continue, FALSE - when max emails was reached
 */
function check_cron_job_emails_limit()
{
   
// Get max number of emails for current cron job:
   
$current_cron_job_emails_limit = get_cron_job_emails_limit();

    if(
$current_cron_job_emails_limit > 0 )
    {    
// If current cron job has a limit for sending of emails:
       
global $executing_cron_task_emails_count;
        if(
$executing_cron_task_emails_count >= $current_cron_job_emails_limit )
        {    
// The limit was reached:
           
global $executing_cron_task_emails_reached, $mail_log_message;
           
$mail_log_message = 'Stopping execution because max number of emails ('.$current_cron_job_emails_limit.') has been sent.';
            if( empty(
$executing_cron_task_emails_reached ) )
            {    
// Append warning to cron log only if max emails is not reached yet:
               
cron_log_append( $mail_log_message."\n", 'warning' );
               
// Set flag to don't show the above warning twice:
               
$executing_cron_task_emails_reached = true;
            }
           
// Stop current job execution:
           
return false;
        }
    }

   
// Allow to continue current cron job execution:
   
return true;
}


/**
 * Get additional error message on failed mail sending
 *
 * @return string
 */
function get_send_mail_error()
{
    global
$Settings;

    if(
$Settings->get( 'email_service' ) == 'smtp' && ! $Settings->get( 'force_email_sending' ) )
    {    
// Only SMTP is used:
       
return T_('Recipient email address does not seem to work.');
    }
    else
    {    
// PHP "mail" function is used by default or it was forced after failed SMTP sending:
       
return T_('Possible reason: the PHP mail() function may have been disabled on the server.');
    }
}


/**
 * Sends an email, wrapping PHP's mail() function.
 * ALL emails sent by b2evolution must be sent through this function (for consistency and for logging)
 *
 * {@link $current_locale} will be used to set the charset.
 *
 * Note: we use a single \n as line ending, though it does not comply to {@link http://www.faqs.org/rfcs/rfc2822 RFC2822}, but seems to be safer,
 * because some mail transfer agents replace \n by \r\n automatically.
 *
 * @todo Unit testing with "nice addresses" This gets broken over and over again.
 *
 * @param string Recipient email address.
 * @param string Recipient name.
 * @param string Subject of the mail
 * @param string|array The message text OR Array: 'charset', 'full', 'html', 'text'
 * @param string From address, being added to headers (we'll prevent injections); see {@link http://securephp.damonkohler.com/index.php/Email_Injection}.
 *               Defaults to {@link GeneralSettings::get('notification_sender_email') } if NULL.
 * @param string From name.
 * @param array Additional headers ( headername => value ). Take care of injection!
 * @param integer User ID
 * @param integer Email Campaign ID
 * @param integer Automation ID
 * @return boolean True if mail could be sent (not necessarily delivered!), false if not - (return value of {@link mail()})
 */
function send_mail( $to, $to_name, $subject, $message, $from = NULL, $from_name = NULL, $headers = array(), $user_ID = NULL, $email_campaign_ID = NULL, $automation_ID = NULL )
{
    global
$servertimenow, $email_send_simulate_only, $email_send_allow_php_mail;

   
/**
     * @var string|NULL This global var stores a last mail log message
     */
   
global $mail_log_message;
   
$mail_log_message = NULL;

    global
$debug, $app_name, $app_version, $current_locale, $current_charset, $evo_charset, $locales, $Debuglog, $Settings, $demo_mode, $mail_log_insert_ID;

   
$message_data = $message;
    if(
is_array( $message_data ) && isset( $message_data['full'] ) )
    {
// If content is multipart
       
$message = $message_data['full'];
    }
    elseif(
is_string( $message_data ) )
    {
// Convert $message_data to array
       
$message_data = array( 'full' => $message );
    }

   
// Replace secret content in the mail logs message body
   
$message = preg_replace( '~\$secret_content_start\$.*\$secret_content_end\$~', '***secret-content-removed***', $message );
   
// Remove secret content marks from the message
   
$message_data = str_replace( array( '$secret_content_start$', '$secret_content_end$' ), '', $message_data );

   
// Memorize email address
   
$to_email_address = $to;

   
$NL = "\r\n";

    if(
$demo_mode )
    {
// Debug mode restriction: Sending email in demo mode is not allowed
       
return false;
    }

    if( !
is_array( $headers ) )
    {
// Make sure $headers is an array
       
$headers = array( $headers );
    }

    if( empty(
$from ) )
    {
       
$from = user_get_notification_sender( $user_ID, 'email' );
    }

    if( empty(
$from_name ) )
    {
       
$from_name = user_get_notification_sender( $user_ID, 'name' );
    }

   
// Pass these data for SMTP mailer
   
$message_data['to_email'] = $to;
   
$message_data['to_name'] = empty( $to_name ) ? NULL : $to_name;
   
$message_data['from_email'] = $from;
   
$message_data['from_name'] = empty( $from_name ) ? NULL : $from_name;

   
$return_path = $Settings->get( 'notification_return_path' );

   
// Add real name into $from...
   
if( ! is_windows() )
    {    
// fplanque: Windows XP, Apache 1.3, PHP 4.4, MS SMTP : will not accept "nice" addresses.
       
if( !empty( $to_name ) )
        {
           
$to = '"'.mail_encode_header_string($to_name).'" <'.$to.'>';
        }
        if( !empty(
$from_name ) )
        {
           
$from = '"'.mail_encode_header_string($from_name).'" <'.$from.'>';
        }
    }

   
$from = mail_sanitize_header_string( $from, true );
   
// From has to go into headers
   
$headers['From'] = $from;
    if( !empty(
$return_path ) )
    {    
// Set a return path
       
$headers['Return-Path'] = $return_path;
    }

   
// echo 'sending email to: ['.htmlspecialchars($to).'] from ['.htmlspecialchars($from).']';

   
$clear_subject = $subject;
   
$subject = mail_encode_header_string($subject);

   
$message = str_replace( array( "\r\n", "\r" ), $NL, $message );

    if( !isset(
$headers['Content-Type'] ) )
    {    
// Specify charset and content-type of email
       
$headers['Content-Type'] = 'text/plain; charset='.$current_charset;
    }
   
$headers['MIME-Version'] = '1.0';

   
$headers['Date'] = gmdate( 'r', $servertimenow );

   
// ADDITIONAL HEADERS:
   
$headers['X-Mailer'] = $app_name.' '.$app_version.' - PHP/'.phpversion();
   
$ip_list = implode( ',', get_ip_list() );
    if( !empty(
$ip_list ) )
    {
// Add X-Remote_Addr param only if its value is not empty
       
$headers['X-Remote-Addr'] = $ip_list;
    }

   
// COMPACT HEADERS:
   
$headerstring = get_mail_headers( $headers, $NL );

   
// Create initial email log with empty message
   
$email_key = generate_random_key();
   
mail_log( $user_ID, $to_email_address, $clear_subject, NULL, $headerstring, 'ready_to_send', $email_key, $email_campaign_ID, $automation_ID );

   
// Replace tracking code placeholders
   
$message = str_replace( array( '$email_key$', '$mail_log_ID$' ), array( $email_key, $mail_log_insert_ID ), $message );
   
$message_data = str_replace( array( '$email_key$', '$mail_log_ID$' ), array( $email_key, $mail_log_insert_ID ), $message_data );

   
// Set an additional parameter for the return path:
   
switch( $Settings->get( 'sendmail_params' ) )
    {
        case
'return':
           
$sendmail_params = '-r $return-address$';
            break;
        case
'from':
           
$sendmail_params = '-f $return-address$';
            break;
        case
'custom':
           
$sendmail_params = $Settings->get( 'sendmail_params_custom' );
            break;
    }
    if( ! empty(
$sendmail_params ) )
    {
       
$additional_parameters = str_replace(
            array(
'$from-address$', '$return-address$' ),
            array(
$message_data['from_email'], ( empty( $return_path ) ? $message_data['from_email'] : $return_path ) ),
           
$sendmail_params );
    }
    else
    {
       
$additional_parameters = '';
    }

   
// Remove email key markers from message that will be sent to actual email
   
$message_data = str_replace( array( '$email_key_start$', '$email_key_end$' ), '', $message_data );

    if(
mail_is_blocked( $to_email_address ) )
    {
// Check if the email address is blocked
       
$mail_log_message = 'Sending mail to "'.$to_email_address.'" FAILED, because this email is marked with spam or permanent errors.';
       
$Debuglog->add( htmlspecialchars( $mail_log_message ), 'error' );

       
update_mail_log( $mail_log_insert_ID, 'blocked', $message );

        return
false;
    }

   
// Simulate email sending when:
    //  - this is forced by config
    //  - php mail sending is disabled by config and SMTP sending is not enabled
   
$simulate_email_sending = $email_send_simulate_only || ( ! $email_send_allow_php_mail && ! $Settings->get( 'smtp_enabled' ) );

    if(
$simulate_email_sending )
    {    
// The email sending is turned on simulation mode, Don't send a real message:
       
$send_mail_result = true;
    }
    else
    {    
// Send email message on real mode:
       
try
        {    
// Try to send:
           
$send_mail_result = evo_mail( $to, $subject, $message_data, $headers, $additional_parameters );
        }
        catch(
Exception $ex )
        {    
// Unexpected error:
           
$send_mail_result = false;
           
// Log the caught error:
           
$mail_log_message = $ex->getMessage();
           
// Insert a returned email's data into DB:
           
load_funcs( 'cron/model/_decode_returned_emails.funcs.php' );
           
$content = dre_limit_by_terminators( $mail_log_message );
           
$email_data = dre_get_email_data( $content, $mail_log_message, 'Empty headers' );
            if( empty(
$email_data['address'] ) )
            {    
// Use current email address if no email address is detected in the error message:
               
$email_data['address'] = $to_email_address;
            }
           
dre_insert_returned_email( $email_data );
        }
    }

    if(
get_cron_job_emails_limit() > 0 )
    {    
// Increase global counter if currently executing cron jobs has a limit for mail sending:
       
global $executing_cron_task_emails_count;
       
$executing_cron_task_emails_count++;
    }


    if( !
$send_mail_result )
    {    
// The message has not been sent successfully
       
$mail_log_message = 'Sending mail from "'.$from.'" to "'.$to.'", Subject "'.$subject.'" FAILED'.( $mail_log_message === NULL ? '' : ', Error: '.$mail_log_message ).'.';
       
update_mail_log( $mail_log_insert_ID, 'error', $message );
        if(
$debug > 1 )
        {
// We agree to die for debugging...
           
debug_die( htmlspecialchars( $mail_log_message ) );
        }
        else
        {
// Soft debugging only....
           
$Debuglog->add( htmlspecialchars( $mail_log_message ), 'error' );
            return
false;
        }
    }

   
$mail_log_message = 'Sent mail from "'.$from.'" to "'.$to.'", Subject "'.$subject.'".';
   
$Debuglog->add( htmlspecialchars( $mail_log_message ) );

   
update_mail_log( $mail_log_insert_ID, ( $simulate_email_sending ? 'simulated' : 'ok' ), $message );

    return
true;
}


/**
 * Sends an email to User
 *
 * @param integer Recipient ID.
 * @param string Subject of the mail
 * @param string Email template name
 * @param array Email template params
 * @param boolean Force to send this email even if the user is not activated. By default not activated user won't get emails.
 *                Pasword reset, and account activation emails must be always forced.
 * @param array Additional headers ( headername => value ). Take care of injection!
 * @param string Use this param if you want use different email address instead of $User->email
 * @return boolean True if mail could be sent (not necessarily delivered!), false if not - (return value of {@link mail()})
 */
function send_mail_to_User( $user_ID, $subject, $template_name, $template_params = array(), $force_on_non_activated = false, $headers = array(), $force_email_address = '' )
{
    global
$UserSettings, $Settings, $current_charset;

   
/**
     * @var string|NULL This global var stores a last mail log message
     */
   
global $mail_log_message;
   
$mail_log_message = NULL;

   
$UserCache = & get_UserCache();
    if(
$User = $UserCache->get_by_ID( $user_ID ) )
    {
        if( !
$User->check_status( 'can_receive_any_message' ) )
        {
// user status doesn't allow to receive nor emails nor private messages
           
$mail_log_message = 'Sending mail to User #'.$User->ID.'('.$User->get( 'login' ).') is FAILED, because user status "'.$User->get( 'status' ).'" doesn\'t allow to receive any message.';
            return
false;
        }

        if( !(
$User->check_status( 'is_validated' ) || $force_on_non_activated ) )
        {
// user is not activated and non activated users should not receive emails, unless force_on_non_activated is turned on
           
$mail_log_message = 'Sending mail to User #'.$User->ID.'('.$User->get( 'login' ).') is FAILED, because user is not validated with status "'.$User->get( 'status' ).'".';
            return
false;
        }

       
// Check if a new email to User with the corrensponding email type is allowed
       
switch( $template_name )
        {
            case
'account_activate':
            case
'account_delete_warning':
                if(
$Settings->get( 'validation_process' ) == 'easy' && !$template_params['is_reminder'] )
                {
// this is not a notification email
                   
break;
                }
           
// Check a day notification limit for user settings "Notify me by email whenever":
           
case 'private_message_new':
               
// 'notify_messages' - "I receive a private message."
           
case 'private_messages_unread_reminder':
               
// 'notify_unread_messages' - "I have unread private messages for more than X seconds."(X = $Settings->get( 'unread_message_reminder_threshold' ))
           
case 'comment_new':
               
// 'notify_comment_mentioned' - "I have been mentioned on a comment.",
                // 'notify_published_comments' - "a comment is published on one of my posts.",
                // 'notify_comment_moderation' - "a comment is posted and I have permissions to moderate it.",
                // 'notify_edit_cmt_moderation' - "a comment is modified and I have permissions to moderate it.",
                // 'notify_meta_comment_mentioned' - "I have been mentioned on an internal comment.",
                // 'notify_meta_comments' - "an internal comment is posted.".
           
case 'comment_spam':
               
// 'notify_spam_cmt_moderation' - "a comment is reported as spam and I have permissions to moderate it."
           
case 'comments_unmoderated_reminder':
               
// 'send_cmt_moderation_reminder' - "comments are awaiting moderation for more than X seconds."(X = $Settings->get( 'comment_moderation_reminder_threshold' ))
           
case 'post_new':
               
// 'notify_post_mentioned' - "I have been mentioned on a post.",
                // 'notify_post_moderation' - "a post is created and I have permissions to moderate it."
                // 'notify_edit_pst_moderation' - "someone proposed a change on a post and I have permissions to moderate it."
           
case 'post_proposed_change':
               
// 'notify_post_proposed' - "someone proposed a change on a post and I have permissions to moderate it."
           
case 'post_assignment':
               
// 'notify_post_assignment' - "a post was assigned to me."
           
case 'posts_unmoderated_reminder':
               
// 'send_pst_moderation_reminder' - "posts are awaiting moderation for more than X seconds."(X = $Settings->get( 'post_moderation_reminder_threshold' ))
           
case 'posts_stale_alert':
               
// 'send_pst_stale_alert' - "there are stale posts and I have permission to moderate them."
           
case 'account_activate':
            case
'account_delete_warning':
               
// 'send_activation_reminder' - "my account was deactivated or is not activated for more than X seconds."(X - $Settings->get( 'activate_account_reminder_threshold' ))
           
case 'account_inactive':
               
// 'send_inactive_reminder' - "my account has been inactive for more than X months."(X - $Settings->get( 'inactive_account_reminder_threshold' ))
           
case 'account_new':
               
// 'notify_new_user_registration' - "a new user has registered."
           
case 'account_activated':
               
// 'notify_activated_account' - "an account was activated."
           
case 'account_closed':
               
// 'notify_closed_account' - "an account was closed."
           
case 'account_reported':
               
// 'notify_reported_account' - "an account was reported."
           
case 'account_changed':
               
// 'notify_changed_account' - "an account was changed."
           
case 'scheduled_task_error_report':
               
// 'notify_cronjob_error' - "a scheduled task ends with an error or timeout."
           
case 'list_new_subscriber':
               
// 'notify_list_new_subscriber' - "one of my Lists gets a new subscriber."
           
case 'list_lost_subscriber':
               
// 'notify_list_lost_subscriber' - "one of my Lists loses a subscriber."
           
case 'automation_owner_notification':
               
// 'notify_automation_owner' - "one of my automations wants to notify me."
               
$email_limit_setting = 'notification_email_limit';
               
$email_counter_setting = 'last_notification_email';
                if( !
check_allow_new_email( $email_limit_setting, $email_counter_setting, $User->ID ) )
                {
// more notification email is not allowed today
                   
$mail_log_message = 'Sending mail to User #'.$User->ID.'('.$User->get( 'login' ).') is FAILED, because user is already limited to receive more notifications for TODAY.';
                    return
false;
                }
                break;
            case
'newsletter':
               
// this is a newsletter email
               
$email_limit_setting = 'newsletter_limit';
               
$email_counter_setting = 'last_newsletter';
                if( !
check_allow_new_email( $email_limit_setting, $email_counter_setting, $User->ID ) )
                {
// more newsletter email is not allowed today
                   
$mail_log_message = 'Sending mail to User #'.$User->ID.'('.$User->get( 'login' ).') is FAILED, because user is already limited to receive more newsletters for TODAY.';
                    return
false;
                }
                break;
            case
'newsletter_test':
               
// this is a newsletter email, used to send test email by current admin
               
$template_name = 'newsletter';
                break;
        }

       
// Update notification sender's info from General settings
       
$User->update_sender();

        switch(
$UserSettings->get( 'email_format', $User->ID ) )
        {    
// Set Content-Type from user's setting "Email format"
           
case 'auto':
               
$template_params['boundary'] = 'b2evo-'.md5( rand() );
               
$headers['Content-Type'] = 'multipart/mixed; boundary="'.$template_params['boundary'].'"';
                break;
            case
'html':
               
$headers['Content-Type'] = 'text/html; charset='.$current_charset;
                break;
            case
'text':
               
$headers['Content-Type'] = 'text/plain; charset='.$current_charset;
                break;
        }

        if( ! isset(
$template_params['recipient_User'] ) )
        {
// Set recipient User, it should be defined for each template because of email footer
           
$template_params['recipient_User'] = $User;
        }

       
// Pass all email headers to template because they may be used there, e.g. in email footer template:
       
$template_params['email_headers'] = $headers;

       
// Get a message text from template file
       
$message = mail_template( $template_name, $UserSettings->get( 'email_format', $User->ID ), $template_params, $User );

       
// Autoinsert user's data
       
$subject = mail_autoinsert_user_data( $subject, $User, 'text', NULL, NULL, $template_params );

       
// erhsatingin > moved to mail_template()
        //$message = mail_autoinsert_user_data( $message, $User );

       
$to_email = !empty( $force_email_address ) ? $force_email_address : $User->email;

       
// Params for email log:
       
$email_campaign_ID = empty( $template_params['ecmp_ID'] ) ? NULL : $template_params['ecmp_ID'];
       
$automation_ID = empty( $template_params['autm_ID'] ) ? NULL : $template_params['autm_ID'];

        if(
send_mail( $to_email, NULL, $subject, $message, NULL, NULL, $headers, $user_ID, $email_campaign_ID, $automation_ID ) )
        {
// email was sent, update last email settings;
           
if( isset( $email_limit_setting, $email_counter_setting ) )
            {
// User Settings(email counters) need to be updated
               
update_user_email_counter( $email_limit_setting, $email_counter_setting, $user_ID );
            }
            return
true;
        }
    }

   
// No user or email could not be sent
   
return false;
}


/**
 * Sends an email to anonymous User
 *
 * @param integer Recipient ID.
 * @param string Subject of the mail
 * @param string Email template name
 * @param array Email template params
 * @param boolean Force to send this email even if the user is not activated. By default not activated user won't get emails.
 *                Pasword reset, and account activation emails must be always forced.
 * @param array Additional headers ( headername => value ). Take care of injection!
 * @param string Use this param if you want use different email address instead of $User->email
 * @return boolean True if mail could be sent (not necessarily delivered!), false if not - (return value of {@link mail()})
 */
function send_mail_to_anonymous_user( $user_email, $user_name, $subject, $template_name, $template_params = array(), $force_on_non_activated = false, $headers = array(), $force_email_address = '' )
{
   
/**
     * @var string|NULL This global var stores a last mail log message
     */
   
global $mail_log_message;
   
$mail_log_message = NULL;

   
// Check if a new email to anonymous user with the corrensponding email type is allowed:
   
switch( $template_name )
    {
        case
'comment_new':
           
// Notify anonymous user of replies:
           
if( isset( $template_params['comment_ID'] ) && ! check_allow_new_anon_email( $template_params['comment_ID'] ) )
            {    
// more notification email is not allowed today:
               
$mail_log_message = 'Sending mail to anonymous user #'.$user_email.'('.$user_name.') is FAILED, because user is already limited to receive more notifications for TODAY.';
                return
false;
            }
            break;
    }

   
$template_params['boundary'] = 'b2evo-'.md5( rand() );
   
$headers['Content-Type'] = 'multipart/mixed; boundary="'.$template_params['boundary'].'"';

    if( ! isset(
$template_params['anonymous_recipient_name'] ) )
    {    
// Set recipient User, it should be defined for each template because of email footer:
       
$template_params['anonymous_recipient_name'] = $user_name;
    }

   
// Get a message text from template file:
   
$message = mail_template( $template_name, 'auto', $template_params );

   
// Autoinsert user's data:
   
$subject = mail_autoinsert_user_data( $subject, NULL, 'text', $user_email, $user_name, $template_params );

   
// Params for email log:
   
$email_campaign_ID = empty( $template_params['ecmp_ID'] ) ? NULL : $template_params['ecmp_ID'];
   
$automation_ID = empty( $template_params['autm_ID'] ) ? NULL : $template_params['autm_ID'];

    if(
send_mail( $user_email, $user_name, $subject, $message, NULL, NULL, $headers, NULL, $email_campaign_ID, $automation_ID ) )
    {    
// Email was sent:
       
if( isset( $template_params['comment_ID'] ) )
        {    
// Anonymous user settings(email counters) need to be updated:
           
update_anon_user_email_counter( $template_params['comment_ID'] );
        }
        return
true;
    }

   
// No user or email could not be sent:
   
return false;
}


/**
 * Autoinsert user's data into subject or message of the email
 *
 * @param string Text
 * @param object User
 * @param string Format: 'html', 'text'
 * @param string Email of anonymous user
 * @param string Name of anonymous user
 * @param array Email template params
 * @return string Text
*/
function mail_autoinsert_user_data( $text, $User = NULL, $format = 'text', $user_email = NULL, $user_name = NULL, $params = array() )
{
    if( !
$User && ! ( $user_email || $user_email ) )
    {    
// No user data:
       
return $text;
    }

    if(
$User )
    {    
// Get data of registered User:
       
global $UserSettings;

        if(
$format == 'html' )
        {
           
$username = $User->get_colored_login( array(
                   
'mask'        => '$avatar$ $login$',
                   
'login_text'  => 'name',
                   
'use_style'   => true,
                   
'extra_class' => 'normal_weight',
                   
'protocol'    => 'http:',
                ) );

           
$user_login = $User->get_colored_login( array(
                   
'mask'        => '$avatar$ $login$',
                   
'use_style'   => true,
                   
'protocol'    => 'http:',
                ) );
        }
        else
        {
           
$username = $User->get_username();
           
$user_login = $User->login;
        }

       
$firstname = $User->get( 'firstname' );
       
$lastname = $User->get( 'lastname' );
       
$firstname_and_login = empty( $firstname ) ? $user_login : $firstname.' ('.$user_login.')';
       
$firstname_or_login = empty( $firstname ) ? $user_login : $firstname;
       
$user_email = $User->email;
       
$user_ID = $User->ID;
       
$unsubscribe_key = '$secret_content_start$'.md5( $User->ID.$User->unsubscribe_key ).'$secret_content_end$';
        if( isset(
$params['template_mode'] ) && $params['template_mode'] == 'preview' )
        {    
// Don't use real value of reminder key on preview email template, e.g. on review message of email campaign:
           
$reminder_key = '$reminder_key$';
        }
        else
        {    
// Use real value of reminder key:
           
$reminder_key = $UserSettings->get( 'last_activation_reminder_key', $user_ID );
            if( empty(
$reminder_key ) && strpos( $text, '$reminder_key$' ) !== false )
            {    
// If reminder key was not generated yet we need create it in order user can active account even if did request the activation email yet:
               
$reminder_key = generate_random_key( 32 );
               
$UserSettings->set( 'last_activation_reminder_key', $reminder_key, $user_ID );
               
$UserSettings->dbupdate();
            }
        }
       
$newsletter_ID = isset( $params['enlt_ID'] ) ? $params['enlt_ID'] : '';
    }
    else
    {    
// Get data of anonymous user:
       
$username = $user_name;
       
$user_login = $user_name;
       
$firstname = $user_name;
       
$lastname = $user_name;
       
$firstname_and_login = $user_name;
       
$firstname_or_login = $user_name;
       
$user_email = $user_email;
       
$user_ID = '';
       
$unsubscribe_key = '';
       
$reminder_key = '';
       
$newsletter_ID = '';
    }

   
$rpls_from = array( '$login$', '$username$', '$firstname$', '$lastname$', '$firstname_and_login$', '$firstname_or_login$', '$email$', '$user_ID$', '$unsubscribe_key$', '$reminder_key$', '$newsletter_ID$' );
   
$rpls_to = array( $user_login, $username, $firstname, $lastname, $firstname_and_login, $firstname_or_login, $user_email, $user_ID, $unsubscribe_key, $reminder_key, $newsletter_ID );

    return
str_replace( $rpls_from, $rpls_to, $text );
}


/**
 * Get a mail message text by template name
 *
 * @param string Template name
 * @param string Email format ( auto | html | text )
 * @param array Params
 * @param object User
 * @return string|array Mail message OR Array of the email contents when message is multipart content
 */
function mail_template( $template_name, $format = 'auto', $params = array(), $User = NULL )
{
    global
$current_charset;
    global
$track_email_image_load, $track_email_click_html, $track_email_click_plain_text;

   
$params = array_merge( array(
           
'add_email_tracking' => true,
           
'template_parts' => array(
                   
'header' => NULL,
                   
'footer' => NULL
               
),
           
'default_template_tag' => NULL
       
), $params );

   
// Set this param to know current template name inside code of email templates like header and footer:
   
$params['template_name'] = $template_name;

    if( !empty(
$params['locale'] ) )
    {
// Switch to locale for current email template
       
locale_temp_switch( $params['locale'] );
    }

   
// Set extension of template
   
$template_exts = array();
    switch(
$format )
    {
        case
'auto':
           
// $template_exts['non-mime'] = '.txt.php'; // The area that is ignored by MIME-compliant clients
           
$template_exts['text'] = '.txt.php';
           
$template_exts['html'] = '.html.php';
           
$boundary = $params['boundary'];
           
$boundary_alt = 'b2evo-alt-'.md5( rand() );
           
$template_headers = array(
                   
'text' => 'Content-Type: text/plain; charset='.$current_charset,
                   
'html' => 'Content-Type: text/html; charset='.$current_charset,
                );
           
// Store all contents in this array for multipart message
           
$template_contents = array(
                   
'charset' => $current_charset, // Charset for email message
                   
'full' => '', // Full content with html and plain
                   
'html' => '', // HTML
                   
'text' => '', // Plain text
               
);
            break;

        case
'html':
           
$template_exts['html'] = '.html.php';
            break;

        case
'text':
           
$template_exts['text'] = '.txt.php';
            break;
    }

   
$template_message = '';

    if( isset(
$boundary, $boundary_alt ) )
    {
// Start new boundary content
       
$template_message .= "\n".'--'.$boundary."\n";
       
$template_message .= 'Content-Type: multipart/alternative; boundary="'.$boundary_alt.'"'."\n\n";
    }

    foreach(
$template_exts as $format => $ext )
    {
       
$formated_message = '';

        if( isset(
$boundary, $boundary_alt ) && $format != 'non-mime' )
        {
// Start new boundary alt content
           
$template_message .= "\n".'--'.$boundary_alt."\n";
        }

        if( isset(
$template_headers[ $format ] ) )
        {
// Header data for each content
           
$template_message .= $template_headers[ $format ]."\n\n";
        }

       
// Get mail template
       
ob_start();
       
emailskin_include( $template_name.$ext, $params );
       
$formated_message .= ob_get_clean();

        if( ! empty(
$User ) )
        {
// Replace $login$ with gender colored link + icon in HTML format, and with simple login text in PLAIN TEXT format
           
$formated_message = mail_autoinsert_user_data( $formated_message, $User, $format, NULL, NULL, $params );
        }
        elseif( ! empty(
$params['anonymous_recipient_name'] ) )
        {    
// Replace anonymous name:
           
$formated_message = str_replace( '$name$', $params['anonymous_recipient_name'], $formated_message );
            if( ! empty(
$params['anonymous_unsubscribe_key'] ) )
            {    
// Replace anonymous unsubscribe key:
               
$formated_message = str_replace( '$unsubscribe_key$', $params['anonymous_unsubscribe_key'], $formated_message );
            }
        }

        if(
$params['add_email_tracking'] )
        {
           
$tracking_params = array(
                   
'content_type' => $format,
                   
'image_load' => isset( $track_email_image_load ) ? $track_email_image_load : true,
                   
'link_click_html' => isset( $track_email_click_html ) ? $track_email_click_html : true ,
                   
'link_click_text' => isset( $track_email_click_plain_text ) ? $track_email_click_plain_text : true,
                   
'template_parts' => $params['template_parts'],
                   
'default_template_tag' => $params['default_template_tag']
                );
           
$formated_message = add_email_tracking( $formated_message, '$mail_log_ID$', '$email_key$', $tracking_params );
        }

       
// Remove template parts markers
       
$template_part_markers = array();
        foreach(
$params['template_parts'] as $part => $row )
        {
           
$template_part_markers[] = '$template-content-'.$part.'-start$';
           
$template_part_markers[] = '$template-content-'.$part.'-end$';
        }
       
$formated_message = str_replace( $template_part_markers, '', $formated_message  );
       
$template_message .= $formated_message;

        if( isset(
$template_contents ) )
        {
// Multipart content
           
$template_contents[ $format ] = $formated_message;
        }
    }

    if( isset(
$boundary, $boundary_alt ) )
    {
// End all boundary contents
       
$template_message .= "\n".'--'.$boundary_alt.'--'."\n";
       
$template_message .= "\n".'--'.$boundary.'--'."\n";
    }

    if( !empty(
$params['locale'] ) )
    {
// Restore previous locale
       
locale_restore_previous();
    }

    if( isset(
$template_contents ) )
    {
// Return array for multipart content
       
$template_contents['full'] = $template_message;
        return
$template_contents;
    }
    else
    {
// Return string if email message contains one content (html or text)
       
return $template_message;
    }
}


/**
 * Include email template from folder /skins_email/custom/ or /skins_email/
 *
 * @param string Template name
 * @param array Params
 */
function emailskin_include( $template_name, $params = array(), $template_part = NULL )
{
    global
$emailskins_path, $rsc_url;

   
/**
    * @var Log
    */
   
global $Debuglog;
    global
$Timer;

   
$timer_name = 'emailskin_include('.$template_name.')';
   
$Timer->resume( $timer_name );

   
$is_customized = false;

   
// Try to include custom template firstly
   
$template_path = $emailskins_path.'custom/'.$template_name;
    if(
file_exists( $template_path ) )
    {
// Include custom template file if it exists
       
$Debuglog->add( 'emailskin_include: '.rel_path_to_base( $template_path ), 'skins' );
        if( ! empty(
$template_part ) )
        {
            echo
'$template-content-'.$template_part.'-start$';
        }
        require
$template_path;
        if( ! empty(
$template_part ) )
        {
            echo
'$template-content-'.$template_part.'-end$';
        }
       
// This template is customized, Don't include standard template
       
$is_customized = true;
    }

    if( !
$is_customized )
    {
// Try to include standard template only if custom template doesn't exist
       
$template_path = $emailskins_path.$template_name;
        if(
file_exists( $template_path ) )
        {
// Include standard template file if it exists
           
$Debuglog->add( 'emailskin_include: '.rel_path_to_base( $template_path ), 'skins' );
            if( ! empty(
$template_part ) )
            {
                echo
'$template-content-'.$template_part.'-start$';
            }
            require
$template_path;
            if( ! empty(
$template_part ) )
            {
                echo
'$template-content-'.$template_part.'-end$';
            }
        }
    }

   
$Timer->pause( $timer_name );
}


/**
 * Get attribute "style" by class name for element in email templates
 *
 * @param string Class name
 * @param boolean TRUE to return string as ' style="css_properties"' otherwise only 'css_properties'
 * @return string
 */
function emailskin_style( $class, $set_attr_name = true )
{
    global
$emailskins_styles;

    if( !
is_array( $emailskins_styles ) )
    {
// Load email styles only first time
       
global $emailskins_path;
        require_once
$emailskins_path.'_email_style.php';

        foreach(
$emailskins_styles as $classes => $styles )
        {
            if(
strpos( $classes, ',' ) !== false )
            {
// This style is used for several classes
               
unset( $emailskins_styles[ $classes ] );
               
$classes = explode( ',', $classes );
                foreach(
$classes as $class_name )
                {
                   
$class_name = trim( $class_name );
                    if( isset(
$emailskins_styles[ $class_name ] ) )
                    {
                       
$emailskins_styles[ $class_name ] .= $styles;
                    }
                    else
                    {
                       
$emailskins_styles[ $class_name ] = $styles;
                    }
                }
            }
        }
    }

    if(
strpos( $class, '+' ) !== false )
    {
// Several classes should be applied this
       
$classes = explode( '+', $class );
       
$style = '';
        foreach(
$classes as $c => $class )
        {
           
$style .= emailskin_style( $class, false );
        }

        return empty(
$style ) ? '' : ( $set_attr_name ? ' style="'.$style.'"' : $style );
    }
    elseif( isset(
$emailskins_styles[ $class ] ) )
    {
// One class
       
$style = trim( str_replace( array( "\r", "\n", "\t" ), '', $emailskins_styles[ $class ] ) );
       
$style = str_replace( ': ', ':', $style );

        return
$set_attr_name ? ' style="'.$style.'"' : $style;
    }

    return
'';
}


/**
 * If first parameter evaluates to true printf() gets called using the first parameter
 * as args and the second parameter as print-pattern
 *
 * @param mixed variable to test and output if it's true or $disp_none is given
 * @param string printf-pattern to use (%s gets replaced by $var)
 * @param string printf-pattern to use, if $var is numeric and > 1 (%s gets replaced by $var)
 * @param string printf-pattern to use if $var evaluates to false (%s gets replaced by $var)
 */
function disp_cond( $var, $disp_one, $disp_more = NULL, $disp_none = NULL )
{
    if(
is_numeric($var) && $var > 1 )
    {
       
printf( ( $disp_more === NULL ? $disp_one : $disp_more ), $var );
        return
true;
    }
    elseif(
$var )
    {
       
printf( $disp_one, $var );
        return
true;
    }
    else
    {
        if(
$disp_none !== NULL )
        {
           
printf( $disp_none, $var );
            return
false;
        }
    }
}


/**
 * Create IMG tag for an action icon.
 *
 * @param string TITLE text (IMG and A link)
 * @param string icon code for {@link get_icon()}
 * @param string URL where the icon gets linked to (empty to not wrap the icon in a link)
 * @param string word to be displayed after icon (if no icon gets displayed, $title will be used instead!)
 * @param integer 1-5: weight of the icon. The icon will be displayed only if its weight is >= than the user setting threshold.
 *                     Use 5, if it's a required icon - all others could get disabled by the user. (Default: 4)
 * @param integer 1-5: weight of the word. The word will be displayed only if its weight is >= than the user setting threshold.
 *                     (Default: 1)
 * @param array Additional attributes to the A tag. The values must be properly encoded for html output (e.g. quotes).
 *        It may also contain these params:
 *         - 'use_js_popup': if true, the link gets opened as JS popup. You must also pass an "id" attribute for this!
 *         - 'use_js_size': use this to override the default popup size ("500, 400")
 *         - 'class': defaults to 'action_icon', if not set; use "" to not use it
 * @param array Attributes for the icon
 * @return string The generated action icon link.
 */
function action_icon( $title, $icon, $url, $word = NULL, $icon_weight = NULL, $word_weight = NULL, $link_attribs = array(), $icon_attribs = array() )
{
    global
$UserSettings;

   
$link_attribs['href'] = $url;
   
$link_attribs['title'] = $title;

    if(
is_null($icon_weight) )
    {
       
$icon_weight = 4;
    }
    if(
is_null($word_weight) )
    {
       
$word_weight = 1;
    }

    if( ! isset(
$link_attribs['class']) )
    {
       
$link_attribs['class'] = 'action_icon';
    }

    if(
get_icon( $icon, 'rollover' ) )
    {
        if( empty(
$link_attribs['class']) )
        {
           
$link_attribs['class'] = 'rollover';
        }
        else
        {
           
$link_attribs['class'] .= ' rollover';
        }

        if(
get_icon( $icon, 'sprite' ) )
        {
// Set class "rollover_sprite" If image uses sprite
           
$link_attribs['class'] .= '_sprite';
        }
    }
   
//$link_attribs['class'] .= $icon != '' ? ' '.$icon : ' noicon';

    // "use_js_popup": open link in a JS popup
    // TODO: this needs to be rewritten with jQuery instead
   
if( false && ! empty($link_attribs['use_js_popup']) )
    {
       
$popup_js = 'var win = new PopupWindow(); win.setUrl( \''.$link_attribs['href'].'\' ); win.setSize(  ); ';

        if( isset(
$link_attribs['use_js_size']) )
        {
            if( ! empty(
$link_attribs['use_js_size']) )
            {
               
$popup_size = $link_attribs['use_js_size'];
            }
        }
        else
        {
           
$popup_size = '500, 400';
        }
        if( isset(
$popup_size) )
        {
           
$popup_js .= 'win.setSize( '.$popup_size.' ); ';
        }
       
$popup_js .= 'win.showPopup(\''.$link_attribs['id'].'\'); return false;';

        if( empty(
$link_attribs['onclick'] ) )
        {
           
$link_attribs['onclick'] = $popup_js;
        }
        else
        {
           
$link_attribs['onclick'] .= $popup_js;
        }
        unset(
$link_attribs['use_js_popup']);
        unset(
$link_attribs['use_js_size']);
    }

   
$display_icon = empty( $UserSettings ) ? false : ($icon_weight >= $UserSettings->get('action_icon_threshold'));
   
$display_word = empty( $UserSettings ) ? false : ($word_weight >= $UserSettings->get('action_word_threshold'));

   
$a_body = '';

    if(
$display_icon || ! $display_word )
    {    
// We MUST display an action icon in order to make the user happy:
        // OR we default to icon because the user doesn't want the word either!!

       
$icon_attribs = array_merge( array(
               
'title' => false, // No need to set attribute "ttile" for icon because parent <a> already has it
           
), $icon_attribs );

        if(
$icon_s = get_icon( $icon, 'imgtag', $icon_attribs, true ) )
        {
           
$a_body .= $icon_s;
        }
        else
        {
// fallback to word
           
$display_word = true;
        }
    }

    if(
$display_word )
    {    
// We MUST display an action word in order to make the user happy:

       
if( $display_icon )
        {
// We already have an icon, display a SHORT word:
           
if( !empty($word) )
            {    
// We have provided a short word:
               
$a_body .= $word;
            }
            else
            {    
// We fall back to alt:
               
$a_body .= get_icon( $icon, 'legend' );
            }
        }
        else
        {    
// No icon display, let's display a LONG word/text:
           
$a_body .= trim( $title, ' .!' );
        }

       
// Add class "hoverlink" for icon with text
       
$link_attribs['class'] .= ' hoverlink';
    }

   
// Format title attribute because it may contains the unexpected chars from translatable strings:
   
if( isset( $link_attribs['title'] ) )
    {
       
$link_attribs['title'] = format_to_output( $link_attribs['title'], 'htmlattr' );
    }

   
// NOTE: We do not use format_to_output with get_field_attribs_as_string() here, because it interferes with the Results class (eval() fails on entitied quotes..) (blueyed)
   
return '<a'.get_field_attribs_as_string( $link_attribs, false ).'>'.$a_body.'</a>';
}


/**
 * Get properties of an icon.
 *
 * Note: to get a file type icon, use {@link File::get_icon()} instead.
 *
 * @uses get_icon_info()
 * @param string icon for what? (key)
 * @param string what to return for that icon ('imgtag', 'alt', 'legend', 'file', 'url', 'size' {@link imgsize()})
 * @param array additional params
 *   - 'class' => class name when getting 'imgtag',
 *   - 'size' => param for 'size',
 *   - 'title' => title attribute for 'imgtag'
 * @param boolean true to include this icon into the legend at the bottom of the page (works for 'imgtag' only)
 * @return mixed False on failure, string on success.
 */
function get_icon( $iconKey, $what = 'imgtag', $params = NULL, $include_in_legend = false )
{
    global
$admin_subdir, $Debuglog, $use_strict;
    global
$conf_path;
    global
$rsc_path, $rsc_uri;

    if( !
function_exists('get_icon_info') )
    {
        require_once
$conf_path.'_icons.php';
    }

   
$icon = get_icon_info($iconKey);
    if( !
$icon )
    {
       
$Debuglog->add('No image defined for '.var_export( $iconKey, true ).'!', 'icons');
        return
false;
    }

    if( !isset(
$icon['file'] ) && $what != 'imgtag' )
    {
       
$icon['file'] = 'icons/icons_sprite.png';
    }

    switch(
$what )
    {
        case
'rollover':
            if( isset(
$icon['rollover'] ) )
            {
// Image has rollover available
               
global $b2evo_icons_type;

                if( isset(
$b2evo_icons_type ) && ( ! empty( $icon['glyph'] ) || ! empty( $icon['fa'] ) ) )
                {
// Glyph and font-awesome icons don't have rollover effect
                   
return false;
                }
                return
$icon['rollover'];
            }
            return
false;
           
/* BREAK */


       
case 'file':
            return
$rsc_path.$icon['file'];
           
/* BREAK */


       
case 'alt':
            if( isset(
$icon['alt'] ) )
            {
// alt tag from $map_iconfiles
               
return $icon['alt'];
            }
            else
            {
// fallback to $iconKey as alt-tag
               
return $iconKey;
            }
           
/* BREAK */


       
case 'legend':
            if( isset(
$icon['legend'] ) )
            {
// legend tag from $map_iconfiles
               
return $icon['legend'];
            }
            else
            if( isset(
$icon['alt'] ) )
            {
// alt tag from $map_iconfiles
               
return $icon['alt'];
            }
            else
            {
// fallback to $iconKey as alt-tag
               
return $iconKey;
            }
           
/* BREAK */


       
case 'class':
            if( isset(
$icon['class']) )
            {
                return
$icon['class'];
            }
            else
            {
                return
'';
            }
           
/* BREAK */

       
case 'url':
            return
$rsc_uri.$icon['file'];
           
/* BREAK */

       
case 'size':
            if( !isset(
$icon['size'] ) )
            {
               
$Debuglog->add( 'No iconsize for ['.$iconKey.']', 'icons' );

               
$icon['size'] = imgsize( $rsc_path.$icon['file'] );
            }

            switch(
$params['size'] )
            {
                case
'width':
                    return
$icon['size'][0];

                case
'height':
                    return
$icon['size'][1];

                case
'widthxheight':
                    return
$icon['size'][0].'x'.$icon['size'][1];

                case
'width':
                    return
$icon['size'][0];

                case
'string':
                    return
'width="'.$icon['size'][0].'" height="'.$icon['size'][1].'"';

                default:
                    return
$icon['size'];
            }
           
/* BREAK */


       
case 'xy':
            if( isset(
$icon['xy'] ) )
            {
// Return data for style property "background-position"
               
return "-".$icon['xy'][0]."px -".$icon['xy'][1]."px";
            }
            return
false;


        case
'sprite':
            if( isset(
$icon['xy'] ) )
            {    
// Image uses spite file
               
return true;
            }
            return
false;
           
/* BREAK */


       
case 'imgtag':
            global
$b2evo_icons_type;

            if( isset(
$b2evo_icons_type ) )
            {
// Specific icons type is defined
               
$current_icons_type = $b2evo_icons_type;
                if(
$current_icons_type == 'fontawesome-glyphicons' )
                {
// Use fontawesome icons as a priority over the glyphicons
                   
$current_icons_type = isset( $icon['fa'] ) ? 'fontawesome' : 'glyphicons';
                }
                switch(
$current_icons_type )
                {
                    case
'glyphicons':
                       
// Use glyph icons of bootstrap
                       
$icon_class_prefix = 'glyphicon glyphicon-';
                       
$icon_param_name = 'glyph';
                       
$icon_content = '&nbsp;';
                        break;

                    case
'fontawesome':
                       
// Use the icons from http://fortawesome.github.io/Font-Awesome/icons/
                       
$icon_class_prefix = 'fa fa-';
                       
$icon_param_name = 'fa';
                       
$icon_content = '';
                        break;
                }
            }

            if( isset(
$icon_class_prefix ) && ! empty( $icon[ $icon_param_name ] ) )
            {
// Use glyph or fa icon if it is defined in icons config
               
if( isset( $params['class'] ) )
                {
// Get class from params
                   
$params['class'] = $icon_class_prefix.$icon[ $icon_param_name ].' '.$params['class'];
                }
                else
                {
// Set default class
                   
$params['class'] = $icon_class_prefix.$icon[ $icon_param_name ];
                }

               
$styles = array();
                if( isset(
$params['color'] ) )
                {    
// Set color from params:
                   
$styles[] = 'color:'.$params['color'];
                    unset(
$params['color'] );
                }
                elseif( isset(
$icon['color-'.$icon_param_name] ) )
                {
// Set a color for icon only for current type
                   
if( $icon['color-'.$icon_param_name] != 'default' )
                    {
                       
$styles[] = 'color:'.$icon['color-'.$icon_param_name];
                    }
                }
                elseif( isset(
$icon['color'] ) )
                {
// Set a color for icon for all types
                   
if( $icon['color'] != 'default' )
                    {
                       
$styles[] = 'color:'.$icon['color'];
                    }
                }
                if( isset(
$icon['color-over'] ) )
                {
// Set a color for mouse over event
                   
$params['data-color'] = $icon['color-over'];
                }
                if( isset(
$icon['toggle-'.$icon_param_name] ) )
                {
// Set a color for mouse over event
                   
$params['data-toggle'] = $icon['toggle-'.$icon_param_name];
                }

                if( ! isset(
$params['title'] ) )
                {
// Use 'alt' for 'title'
                   
if( isset( $params['alt'] ) )
                    {
                       
$params['title'] = $params['alt'];
                        unset(
$params['alt'] );
                    }
                    else if( ! isset(
$params['alt'] ) && isset( $icon['alt'] ) )
                    {
                       
$params['title'] = $icon['alt'];
                    }
                }

                if( isset(
$params['title'] ) && $params['title'] === false )
                {    
// Disable title:
                   
unset( $params['title'] );
                }

               
// Format title and alt attributes because they may contain the unexpected chars from translatable strings:
               
if( isset( $params['title'] ) )
                {
                   
// Use 'htmlspecialchars' format instead of 'htmlattr' because html tags should not be stripped e.g. in bootsrap tooltips:
                   
$params['title'] = format_to_output( $params['title'], 'htmlspecialchars' );
                }
                if( isset(
$params['alt'] ) )
                {
                   
$params['alt'] = format_to_output( $params['alt'], 'htmlattr' );
                }

                if( isset(
$icon['size-'.$icon_param_name] ) )
                {
// Set a size for icon only for current type
                   
if( isset( $icon['size-'.$icon_param_name][0] ) )
                    {
// Width
                       
$styles['width'] = 'width:'.$icon['size-'.$icon_param_name][0].'px';
                    }
                    if( isset(
$icon['size-'.$icon_param_name][1] ) )
                    {
// Height
                       
$styles['height'] = 'height:'.$icon['size-'.$icon_param_name][1].'px';
                    }
                }

                if( isset(
$params['style'] ) )
                {
// Keep styles from params
                   
$styles[] = $params['style'];
                }
                if( ! empty(
$styles ) )
                {
// Init attribute 'style'
                   
$params['style'] = implode( ';', $styles );
                }

               
// Add all the attributes:
               
$params = get_field_attribs_as_string( $params, false );

               
$r = '<span'.$params.'>'.$icon_content.'</span>';
            }
            elseif( ! isset(
$icon['file'] ) )
            {
// Use span tag with sprite instead of img
               
$styles = array();

                if( isset(
$params['xy'] ) )
                {
// Get background position from params
                   
$styles[] = "background-position: ".$params['xy'][0]."px ".$params['xy'][1]."px";
                    unset(
$params['xy'] );
                }
                else if( isset(
$icon['xy'] ) )
                {
// Set background position in the icons_sprite.png
                   
$styles[] = "background-position: -".$icon['xy'][0]."px -".$icon['xy'][1]."px";
                }

                if( isset(
$params['size'] ) )
                {
// Get sizes from params
                   
$icon['size'] = $params['size'];
                    unset(
$params['size'] );
                }
                if( isset(
$icon['size'] ) )
                {
// Set width & height
                   
if( $icon['size'][0] != 16 )
                    {
                       
$styles[] = "width: ".$icon['size'][0]."px";
                    }
                    if(
$icon['size'][1] != 16 )
                    {
                       
$styles[] = "height: ".$icon['size'][1]."px; line-height: ".$icon['size'][1]."px";
                    }
                }

                if( isset(
$params['style'] ) )
                {
// Get styles from params
                   
$styles[] = $params['style'];
                }
                if(
count( $styles ) > 0 )
                {
                   
$params['style'] = implode( '; ', $styles);
                }

                if( ! isset(
$params['title'] ) )
                {    
// Use 'alt' for 'title'
                   
if( isset( $params['alt'] ) )
                    {
                       
$params['title'] = $params['alt'];
                        unset(
$params['alt'] );
                    }
                    else if( ! isset(
$params['alt'] ) && isset( $icon['alt'] ) )
                    {
                       
$params['title'] = $icon['alt'];
                    }
                }

                if( isset(
$params['title'] ) && $params['title'] === false )
                {    
// Disable title:
                   
unset( $params['title'] );
                }

               
// Format title and alt attributes because they may contain the unexpected chars from translatable strings:
               
if( isset( $params['title'] ) )
                {
                   
$params['title'] = format_to_output( $params['title'], 'htmlattr' );
                }
                if( isset(
$params['alt'] ) )
                {
                   
$params['alt'] = format_to_output( $params['alt'], 'htmlattr' );
                }

                if( isset(
$params['class'] ) )
                {    
// Get class from params
                   
$params['class'] = 'icon '.$params['class'];
                }
                else
                {    
// Set default class
                   
$params['class'] = 'icon';
                }

               
// Add all the attributes:
               
$params = get_field_attribs_as_string( $params, false );

               
$r = '<span'.$params.'>&nbsp;</span>';
            }
            else
            {
// Use img tag
               
$r = '<img src="'.$rsc_uri.$icon['file'].'" ';

                if( !
$use_strict )
                {    
// Include non CSS fallbacks - transitional only:
                   
$r .= 'border="0" align="top" ';
                }

               
// Include class (will default to "icon"):
               
if( ! isset( $params['class'] ) )
                {
                    if( isset(
$icon['class']) )
                    {    
// This icon has a class
                       
$params['class'] = $icon['class'];
                    }
                    else
                    {
                       
$params['class'] = '';
                    }
                }

               
// Include size (optional):
               
if( isset( $icon['size'] ) )
                {
                   
$r .= 'width="'.$icon['size'][0].'" height="'.$icon['size'][1].'" ';
                }

               
// Include alt (XHTML mandatory):
               
if( ! isset( $params['alt'] ) )
                {
                    if( isset(
$icon['alt'] ) )
                    {
// alt-tag from $map_iconfiles
                       
$params['alt'] = $icon['alt'];
                    }
                    else
                    {
// $iconKey as alt-tag
                       
$params['alt'] = $iconKey;
                    }
                }

               
// Format alt attribute because it may contains the unexpected chars from translatable strings:
               
if( isset( $params['alt'] ) )
                {
                   
$params['alt'] = format_to_output( $params['alt'], 'htmlattr' );
                }

               
// Add all the attributes:
               
$r .= get_field_attribs_as_string( $params, false );

               
// Close tag:
               
$r .= '/>';


                if(
$include_in_legend && ( $IconLegend = & get_IconLegend() ) )
                {
// This icon should be included into the legend:
                   
$IconLegend->add_icon( $iconKey );
                }
            }
            return
$r;
           
/* BREAK */

       
case 'noimg':
            global
$b2evo_icons_type;

            if( isset(
$b2evo_icons_type ) )
            {
// Specific icons type is defined
               
$current_icons_type = $b2evo_icons_type;
                if(
$current_icons_type == 'fontawesome-glyphicons' )
                {
// Use fontawesome icons as a priority over the glyphicons
                   
$current_icons_type = isset( $icon['fa'] ) ? 'fontawesome' : 'glyphicons';
                }
                switch(
$current_icons_type )
                {
                    case
'glyphicons':
                       
// Use glyph icons of bootstrap
                       
$icon_param_name = 'glyph';
                        break;

                    case
'fontawesome':
                       
// Use the icons from http://fortawesome.github.io/Font-Awesome/icons/
                       
$icon_param_name = 'fa';
                        break;
                }
            }

           
$styles = array();
            if( isset(
$icon_param_name ) && ! empty( $icon[ $icon_param_name ] ) )
            {
// Use glyph or fa icon if it is defined in icons config
               
if( isset( $icon['size-'.$icon_param_name] ) )
                {
// Set a size for icon only for current type
                   
if( isset( $icon['size-'.$icon_param_name][0] ) )
                    {
// Width
                       
$styles['width'] = 'width:'.$icon['size-'.$icon_param_name][0].'px';
                    }
                    if( isset(
$icon['size-'.$icon_param_name][1] ) )
                    {
// Height
                       
$styles['width'] = 'height:'.$icon['size-'.$icon_param_name][1].'px';
                    }
                    if( isset(
$icon['size'] ) )
                    {
// Unset size for sprite icon
                       
unset( $icon['size'] );
                    }
                }
            }

           
// Include size (optional):
           
if( isset( $icon['size'] ) )
            {
               
$params['size'] = $icon['size'];
            }
           
$styles[] = 'margin:0 2px';

            if( isset(
$params['style'] ) )
            {
// Keep styles from params
               
$styles[] = $params['style'];
            }
            if( ! empty(
$styles ) )
            {
// Init attribute 'style'
               
$params['style'] = implode( ';', $styles );
            }

            return
get_icon( 'pixel', 'imgtag', $params );
           
/* BREAK */
            /*
            $blank_icon = get_icon_info('pixel');

            $r = '<img src="'.$rsc_uri.$blank_icon['file'].'" ';

            // TODO: dh> add this only for !$use_strict, like above?
            // Include non CSS fallbacks (needed by bozos... and basic skin):
            $r .= 'border="0" align="top" ';

            // Include class (will default to "noicon"):
            if( ! isset( $params['class'] ) )
            {
                if( isset($icon['class']) )
                {    // This icon has a class
                    $params['class'] = $icon['class'];
                }
                else
                {
                    $params['class'] = 'no_icon';
                }
            }

            // Include size (optional):
            if( isset( $icon['size'] ) )
            {
                $r .= 'width="'.$icon['size'][0].'" height="'.$icon['size'][1].'" ';
            }

            // Include alt (XHTML mandatory):
            if( ! isset( $params['alt'] ) )
            {
                $params['alt'] = '';
            }

            // Add all the attributes:
            $r .= get_field_attribs_as_string( $params, false );

            // Close tag:
            $r .= '/>';

            return $r;*/
            /* BREAK */
   
}
}


/**
 * @param string date (YYYY-MM-DD)
 * @param string time
 */
function form_date( $date, $time = '' )
{
    return
substr( $date.'          ', 0, 10 ).' '.$time;
}


/**
 * Get list of client IP addresses from REMOTE_ADDR and HTTP_X_FORWARDED_FOR,
 * in this order. '' is used when no IP could be found.
 *
 * @param boolean True, to get only the first IP (probably REMOTE_ADDR)
 * @param boolean True, to convert IPv6 to IPv4 format
 * @return array|string Depends on first param.
 */
function get_ip_list( $firstOnly = false, $convert_to_ipv4 = false )
{
   
$r = array();

    if( ! empty(
$_SERVER['REMOTE_ADDR'] ) )
    {
        foreach(
explode( ',', $_SERVER['REMOTE_ADDR'] ) as $l_ip )
        {
           
$l_ip = trim( $l_ip );
            if( ! empty(
$l_ip ) )
            {
                if(
$convert_to_ipv4 )
                {
// Convert IP address to IPv4 format(if it is in IPv6 format)
                   
$l_ip = int2ip( ip2int( $l_ip ) );
                }
               
$r[] = $l_ip;
            }
        }
    }

    if( ! empty(
$_SERVER['HTTP_X_FORWARDED_FOR'] ) )
    {
// IP(s) behind Proxy - this can be easily forged!
       
foreach( explode( ',', $_SERVER['HTTP_X_FORWARDED_FOR'] ) as $l_ip )
        {
           
$l_ip = trim( $l_ip );
            if( ! empty(
$l_ip ) && $l_ip != 'unknown' )
            {
                if(
$convert_to_ipv4 )
                {
// Convert IP address to IPv4 format(if it is in IPv6 format)
                   
$l_ip = int2ip( ip2int( $l_ip ) );
                }
               
$r[] = $l_ip;
            }
        }
    }

    if( ! isset(
$r[0] ) )
    {
// No IP found.
       
$r[] = '';
    }

   
// Remove the duplicates
   
$r = array_unique( $r );

    return
$firstOnly ? $r[0] : $r;
}


/**
 * Get list of IP addresses with link to back-office page if User has an access
 *
 * @param object|NULL User
 * @param array|NULL List of IP addresses
 * @param string Text of link, Use '#' to display IP address
 * @return array List of IP addresses
 */
function get_linked_ip_list( $ip_list = NULL, $User = NULL, $link_text = '#' )
{
    if(
$User === NULL )
    {
// Get current User by default
       
global $current_User;
       
$User = & $current_User;
    }

    if(
$ip_list === NULL )
    {
// Get IP addresses by function get_ip_list()
       
$ip_list = get_ip_list( false, true );
    }

    if( ! empty(
$User ) &&
       
$User->check_perm( 'admin', 'restricted' ) &&
       
$User->check_perm( 'spamblacklist', 'view' ) )
    {
// User has an access to backoffice, Display a link for each IP address
       
global $admin_url;
        foreach(
$ip_list as $i => $ip_address )
        {
            if(
$link_text == '#' )
            {
// Use IP address aslink text
               
$link_text = $ip_address;
            }
           
$ip_list[ $i ] = '<a href="'.$admin_url.'?ctrl=antispam&amp;tab3=ipranges&amp;ip_address='.$ip_address.'">'.$link_text.'</a>';
        }
    }

    return
$ip_list;
}


/**
 * Get the base domain (without protocol and any subdomain) of an URL.
 *
 * Gets a max of 3 domain parts (x.y.tld)
 *
 * @param string URL
 * @return string the base domain (may become empty, if found invalid)
 */
function get_base_domain( $url )
{
    global
$evo_charset;

   
// Chop away the protocol part(http,htpps,ftp) and the path:
   
$domain = preg_replace( '~^([a-z]+://)?([^:/#]+)(.*)$~i', '\\2', $url );

    if( empty(
$domain ) || preg_match( '~^(\d+\.)+\d+$~', $domain ) )
    {    
// Empty or All numeric = IP address, don't try to cut it any further:
       
return $domain;
    }

   
// Get the base domain up to 2 or 3 levels (x.y.tld):
    // NOTE: "_" is not really valid, but for Windows it is..
    // NOTE: \w includes "_"

    // Convert URL to IDN:
   
$domain = idna_encode( $domain );

    if(
preg_match( '~\.(com|net|org|int|edu|gov|mil)$~i', $domain ) )
    {    
// Use max 2 level domain for very well known TLDs:
        // (for example: "sub3.sub2.sub1.domain.com" will be "domain.com")
       
$max_level = 2;
    }
    else
    {    
// Use max 3 level domain for all others:
        // (for example: "sub3.sub2.sub1.domain.fr" will be "sub1.domain.fr")
       
$max_level = 3;
    }

   
// Limit domain by 2 or 3 level depending on TLD:
   
if( ! preg_match( '~ ( \w (\w|-|_)* \. ){0,'.( $max_level - 1 ).'}   \w (\w|-|_)* $~ix', $domain, $match ) )
    {    
// Return an empty if domain doesn't match to proper format:
       
return '';
    }

   
// Convert all symbols of domain name to UTF-8:
   
$domain = convert_charset( idna_decode( $match[0] ), $evo_charset, 'UTF-8' );

   
// Remove any prefix like "www.", "www2.", "www9999." and etc.:
   
$domain = preg_replace( '~^www[0-9]*\.~i', '', $domain );

    return
$domain;
}


/**
 * Generate login from registration information
 *
 * @param string Email address
 * @param string First name
 * @param string Last name
 * @param string Nickname
 * @param boolean Use random alphanumeric string as login
 * @return string Generated login
 */
function generate_login_from_register_info( $email = NULL, $firstname = NULL, $lastname = NULL, $nickname = NULL, $use_random = false )
{
    global
$Settings;

    if( ! empty(
$firstname ) || ( ! empty( $lastname ) && ( $Settings->get( 'registration_no_username') == 'firstname.lastname' ) ) )
    {
// Firstname or lastname given, let's use these:
       
$login = array();
        if( ! empty(
$firstname ) )
        {
           
$login[] = trim( $firstname );
        }

        if(
$Settings->get( 'registration_no_username' ) == 'firstname.lastname' )
        {    
// We can use lastname too:
           
if( ! empty( $lastname ) )
            {
               
$login[] = trim( $lastname );
            }
        }
       
$login = preg_replace( '/[\s]+/', '_', utf8_strtolower( implode( '.', $login ) ) );
       
$login = generate_login_from_string( $login );
    }
    elseif( ! empty(
$email ) )
    {
// Get the login from email address:
       
$login = preg_replace( '/^([^@]+)@(.+)$/', '$1', utf8_strtolower( $email ) );
       
$login = preg_replace( '/[\'"><@\s]/', '', $login );

        if(
strpos( $login, '.' ) && ( $Settings->get( 'registration_no_username' ) == 'firstname' ) )
        {
// Get only the part before the "." if it has one
           
$temp_login = $login;
           
$login = substr( $login, 0, strpos( $login, '.' ) );
           
$login = generate_login_from_string( $login );

            if( empty(
$login ) )
            {
// Resulting login empty, use full email address
               
$login = generate_login_from_string( $temp_login );
            }
        }
        else
        {
           
$login = generate_login_from_string( $login );
        }
    }
    elseif( ! empty(
$nickname ) )
    {
       
$login = preg_replace( '/[\s]+/', '_', utf8_strtolower( trim( $nickname ) ) );
       
$login = generate_login_from_string( $login );
    }
    elseif(
$use_random )
    {    
// Nothing else to use as login, use random numbers:
       
$login = 'user_'.rand( 1, 999 );
       
$login = generate_login_from_string( $login );
    }
    else
    {
        return
'';
    }

    return
$login;
}


/**
 * Generate login from string
 *
 * @param string string to generate login from
 * @return string login
 */
function generate_login_from_string( $login )
{
    global
$Settings;

   
// Normalize login
   
load_funcs('locales/_charset.funcs.php');
   
$login = replace_special_chars( $login, NULL, true );

    if(
$Settings->get( 'strict_logins' ) )
    {
// We allow only the plain ACSII characters, digits, the chars _ and . and -
       
$login = preg_replace( '/[^A-Za-z0-9_.\-]/', '', $login );
    }
    else
    {
// We allow any character that is not explicitly forbidden in Step 1
        // Enforce additional limitations
       
$login = preg_replace( '|%([a-fA-F0-9][a-fA-F0-9])|', '', $login ); // Kill octets
       
$login = preg_replace( '/&.+?;/', '', $login ); // Kill entities
   
}

   
$login = preg_replace( '/^usr_/i', '', $login );

   
// Trim to allowed login length
   
$max_login_length = 20;
    if(
strlen( $login ) > $max_login_length )
    {
       
$login = substr( $login, 0, $max_login_length );
    }

    if( ! empty(
$login ) )
    {
       
// Check and search free login name if current is already in use
       
$login_name = $login;
       
$login_number = 1;
       
$UserCache = & get_UserCache();
        while(
$UserCache->get_by_login( $login_name ) )
        {
           
$num_suffix_length = strlen( $login_number );
            if(
strlen( $login_name ) + $num_suffix_length > $max_login_length )
            {
               
$login_name = substr( $login, 0, $max_login_length - $num_suffix_length ).$login_number;
            }
            else
            {
               
$login_name = $login.$login_number;
            }
           
$login_number++;
        }
       
$login = $login_name;
    }

    return
$login;
}


/**
 * Generate a valid key of size $length.
 *
 * @param integer length of key
 * @param string chars to use in generated key
 * @return string key
 */
function generate_random_key( $length = 32, $keychars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' )
{
   
$key = '';
   
$rnd_max = strlen($keychars) - 1;

    for(
$i = 0; $i < $length; $i++ )
    {
       
$key .= $keychars[mt_rand(0, $rnd_max)]; // get a random character out of $keychars
   
}

    return
$key;
}


/**
 * Generate a random password with no ambiguous chars
 *
 * @param integer length of password
 * @return string password
 */
function generate_random_passwd( $length = NULL )
{
   
// fp> NOTE: do not include any characters that would make autogenerated passwords ambiguous
    // 1 (one) vs l (L) vs I (i)
    // O (letter) vs 0 (digit)

   
if( empty($length) )
    {
       
$length = rand( 8, 14 );
    }

    return
generate_random_key( $length, 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789' );
}


function
is_create_action( $action )
{
   
$action_parts = explode( '_', $action );

    switch(
$action_parts[0] )
    {
        case
'new':
        case
'new_switchtab':
        case
'copy':
        case
'create':    // we return in this state after a validation error
       
case 'preview':
            return
true;

        case
'edit':
        case
'edit_switchtab':
        case
'propose':
        case
'update':    // we return in this state after a validation error
       
case 'delete':
       
// The following one's a bit far fetched, but can happen if we have no sheet display:
       
case 'unlink':
        case
'view':
        case
'extract':
            return
false;

        default:
           
debug_die( 'Unhandled action in form: '.strip_tags($action_parts[0]) );
    }
}


/**
 * Compact a date in a number keeping only integer value of the string
 *
 * @param string date
 */
function compact_date( $date )
{
    return
preg_replace( '#[^0-9]#', '', $date );
}


/**
 * Decompact a date in a date format ( Y-m-d h:m:s )
 *
 * @param string date
 */
function decompact_date( $date )
{
   
$date0 = $date;

    return  
substr($date0,0,4).'-'.substr($date0,4,2).'-'.substr($date0,6,2).' '
                               
.substr($date0,8,2).':'.substr($date0,10,2).':'.substr($date0,12,2);
}

/**
 * Check the format of the phone number param and
 * format it in a french number if it is.
 *
 * @param string phone number
 */
function format_phone( $phone, $hide_country_dialing_code_if_same_as_locale = true )
{
    global
$CountryCache;

   
$dialing_code = NULL;

    if(
substr( $phone, 0, 1 ) == '+' )
    {    
// We have a dialing code in the phone, so we extract it:
       
$dialing_code = $CountryCache->extract_country_dialing_code( substr( $phone, 1 ) );
    }

    if( !
is_null( $dialing_code ) && ( locale_dialing_code() == $dialing_code )
            &&
$hide_country_dialing_code_if_same_as_locale )
    {    
// The phone dialing code is same as locale and we want to hide it in this case
       
if( ( strlen( $phone ) - strlen( $dialing_code ) ) == 10 )
        {    
// We can format it like a french phone number ( 0x.xx.xx.xx.xx )
           
$phone_formated = format_french_phone( '0'.substr( $phone, strlen( $dialing_code )+1 ) );
        }
        else
        {
// ( 0xxxxxxxxxxxxxx )
           
$phone_formated = '0'.substr( $phone, strlen( $dialing_code )+1 );
        }

    }
    elseif( !
is_null( $dialing_code ) )
    {    
// Phone has a dialing code
       
if( ( strlen( $phone ) - strlen( $dialing_code ) ) == 10 )
        {
// We can format it like a french phone number with the dialing code ( +dialing x.xx.xx.xx.xx )
           
$phone_formated = '+'.$dialing_code.format_french_phone( ' '.substr( $phone, strlen( $dialing_code )+1 ) );
        }
        else
        {
// ( +dialing  xxxxxxxxxxx )
           
$phone_formated = '+'.$dialing_code.' '.substr( $phone, strlen( $dialing_code )+1 );
        }
    }
    else
    {
        if(
strlen( $phone ) == 10 )
        {
//  We can format it like a french phone number ( xx.xx.xx.xx.xx )
           
$phone_formated = format_french_phone( $phone );
        }
        else
        {    
// We don't format phone: TODO generic format phone ( xxxxxxxxxxxxxxxx )
           
$phone_formated = $phone;
        }
    }

    return
$phone_formated;
}


/**
 * Format a string in a french phone number
 *
 * @param string phone number
 */
function format_french_phone( $phone )
{
    return
substr($phone, 0 , 2).'.'.substr($phone, 2, 2).'.'.substr($phone, 4, 2)
                    .
'.'.substr($phone, 6, 2).'.'.substr($phone, 8, 2);
}


/**
 * Get the manual url for the given topic
 *
 * @param string topic
 * @return string url to the manual
 */
function get_manual_url( $topic )
{
   
// fp> TODO: this below is a temmporary hack while we work on the new manual:
   
return 'http://b2evolution.net/man/'.str_replace( '_', '-', strtolower( rawurlencode( $topic ) ) );
}


/**
 * Generate a link to a online help resource.
 * testing the concept of online help (aka webhelp).
 * this function should be relocated somewhere better if it is taken onboard by the project
 *
 * @todo replace [?] with icon,
 * @todo write url suffix dynamically based on topic and language
 *
 * QUESTION: launch new window with javascript maybe?
 * @param string Topic
 *        The topic should be in a format like [\w]+(/[\w]+)*, e.g features/online_help.
 * @param string link text, leave it NULL to get link with manual icon
 * @param string a word to be displayed after the manual icon (if no icon gets displayed, $title will be used instead!)
 * @param integer 1-5: weight of the word. The word will be displayed only if its weight is >= than the user setting threshold. (Default: 1)
 * @return string
 */
function get_manual_link( $topic, $link_text = NULL, $action_word = NULL, $word_weight = 1 )
{
    global
$online_help_links;

    if(
$online_help_links )
    {
       
$manual_url = get_manual_url( $topic );

        if(
$link_text == NULL )
        {
            if(
$action_word == NULL )
            {
               
$action_word = T_('Manual');
            }
           
$webhelp_link = action_icon( T_('Open relevant page in online manual'), 'manual', $manual_url, $action_word, 5, $word_weight, array( 'target' => '_blank' ) );
        }
        else
        {
           
$webhelp_link = '<a href="'.$manual_url.'" target = "_blank">'.$link_text.'</a>';
        }

        return
' '.$webhelp_link;
    }
    else
    {
        return
'';
    }
}


/**
 * Build a string out of $field_attribs, with each attribute
 * prefixed by a space character.
 *
 * @param array Array of field attributes.
 * @param boolean Use format_to_output() for the attributes?
 * @return string
 */
function get_field_attribs_as_string( $field_attribs, $format_to_output = true )
{
   
$r = '';
    foreach(
$field_attribs as $l_attr => $l_value )
    {
        if(
$l_value === NULL )
        {
// don't generate empty attributes (it may be NULL if we pass 'value' => NULL as field_param for example, because isset() does not match it!)
            // sam2kb> what about alt="" how do we handle this?
            // I've removed the "=== ''" check now. Should not do any harm. IIRC NULL is what we want to avoid here.
           
continue;
        }

        if(
$format_to_output )
        {
           
$r .= ' '.$l_attr.'="'.format_to_output( $l_value, 'formvalue' ).'"';
        }
        else
        {
           
$r .= ' '.$l_attr.'="'.$l_value.'"';
        }
    }

    return
$r;
}


/**
 * Update values of HTML tag attributes
 * This will only update the first HTML tag at the beginning of the passed param.
 *
 * @param string HTML tag
 * @param array Attributes
 * @param array Actions for each attribute:
 *              'append'  - Append to existing attribute value (Default for all)
 *              'skip'    - Skip if attribute already exists
 *              'replace' - Replace attribute to new value completely
 * @return string Updated HTML tag
 */
function update_html_tag_attribs( $html_tag, $new_attribs, $attrib_actions = array() )
{
   
// Check for a valid html tag at the beginning of the string:
   
if( ! preg_match( '#^<([\S]+)[^>]*>#i', $html_tag, $tag_match ) )
    {    
// Wrong HTML tag format, Return original string:
       
return $html_tag;
    }

   
$html_tag_name = $tag_match[1];

   
// Get the remaining string after the first HTML tag:
   
$trailing_str = str_replace( $tag_match[0], '', $html_tag );

   
$old_attribs = array();
   
$updated_attribs = array();
    if(
preg_match_all( '@(\S+)=("|\'|)(.*)("|\'|>)@isU', $html_tag, $attr_matches ) )
    {    
// Get all existing attributes:
       
foreach( $attr_matches[1] as $o => $old_attr_name )
        {
           
$old_attribs[ $old_attr_name ] = $attr_matches[3][ $o ];
            if( ! isset(
$new_attribs[ $old_attr_name ] ) )
            {    
// This attribute is not updated, keep current value:
               
$updated_attribs[] = $old_attr_name.'="'.format_to_output( $attr_matches[3][ $o ], 'formvalue' ).'"';
            }
        }
    }

    foreach(
$new_attribs as $new_attrib_name => $new_attrib_value )
    {
        if( isset(
$old_attribs[ $new_attrib_name ] ) )
        {    
// If attribute exists in original HTML tag then Update it depending on selected action:
           
$attrib_action = isset( $attrib_actions[ $new_attrib_name ] ) ? $attrib_actions[ $new_attrib_name ] : 'append';
            switch(
$attrib_action )
            {
                case
'skip':
                   
// Don't update old value:
                   
$new_attrib_value = $old_attribs[ $new_attrib_name ];
                    break;

                case
'replace':
                   
// Replace  old value with new:
                    // $new_attrib_value = $new_attrib_value;
                   
break;

                case
'append':
                default:
                   
// Append new value to old:
                   
$new_attrib_value = $old_attribs[ $new_attrib_name ].' '.$new_attrib_value;
                    break;
            }
        }
       
// ELSE If attribute doesn't exist in original HTML tag then create new one.

       
$updated_attribs[] = $new_attrib_name.'="'.format_to_output( $new_attrib_value, 'formvalue' ).'"';
    }

    return
'<'.$html_tag_name.' '.implode( ' ', $updated_attribs ).'>'.$trailing_str;
}


/**
 * Is the current page an install page?
 *
 * @return boolean
 */
function is_install_page()
{
    global
$is_install_page;

    return isset(
$is_install_page ) && $is_install_page === true; // check for type also, because of register_globals!
}


/**
 * Is the current page an admin/backoffice page?
 *
 * @return boolean
 */
function is_admin_page()
{
    global
$is_admin_page;

    return isset(
$is_admin_page ) && $is_admin_page === true; // check for type also, because of register_globals!
}


/**
 * Is the current page a default 'Front' page of a blog?
 *
 * @return boolean
 */
function is_front_page()
{
    global
$is_front;

    return isset(
$is_front ) && $is_front === true;
}


/**
 * Is pro version?
 *
 * @return boolean
 */
function is_pro()
{
    global
$app_pro;

    return isset(
$app_pro ) && $app_pro === true;
}


/**
 * Check if current version is PRO otherwise redirect to info page about PRO version
 */
function check_pro()
{
    if( !
is_pro() )
    {    
// Restrict the checking feature for NOT PRO version:
       
global $admin_url;

       
header_redirect( $admin_url.'?ctrl=pro_only' );
    }
}


/**
 * Does the given url require logged in user
 *
 * @param string url
 * @param boolean set true to also check if url is login screen or not
 * @return boolean
 */
function require_login( $url, $check_login_screen )
{
    global
$Settings, $dispatcher;
    if(
preg_match( '#/'.preg_quote( $dispatcher, '#' ).'([&?].*)?$#', $url ) )
    {
// admin always require logged in user
       
return true;
    }

    if(
$check_login_screen &&  preg_match( '#/login.php([&?].*)?$#', $url ) )
    {
        return
true;
    }

   
$disp_names = 'threads|messages|contacts';
    if( !
$Settings->get( 'allow_anonymous_user_list' ) )
    {
       
$disp_names .= '|users';
    }
    if( !
$Settings->get( 'allow_anonymous_user_profiles' ) )
    {
       
$disp_names .= '|user';
    }
    if(
$check_login_screen )
    {
       
$disp_names .= '|login';
    }
    if(
preg_match( '#disp=('.$disp_names.')#', $url ) )
    {
// $url require logged in user
       
return true;
    }

    return
false;
}


/**
 * Implode array( 'x', 'y', 'z' ) to something like 'x, y and z'. Useful for displaying list to the end user.
 *
 * If there's one element in the table, it is returned.
 * If there are at least two elements, the last one is concatenated using $implode_last, while the ones before are imploded using $implode_by.
 *
 * @todo dh> I don't think using entities/HTML as default for $implode_last is sane!
 *           Use "&" instead and make sure that the output for HTML is HTML compliant..
 * @todo Support for locales that have a different kind of enumeration?!
 * @return string
 */
function implode_with_and( $arr, $implode_by = ', ', $implode_last = ' &amp; ' )
{
    switch(
count($arr) )
    {
        case
0:
            return
'';

        case
1:
           
$r = array_shift($arr);
            return
$r;

        default:
           
$r = implode( $implode_by, array_slice( $arr, 0, -1 ) )
                .
$implode_last.array_pop( $arr );
            return
$r;
    }
}


/**
 * Display an array as a list:
 *
 * @param array
 * @param string
 * @param string
 * @param string
 * @param string
 * @param string
 */
function display_list( $items, $list_start = '<ul>', $list_end = '</ul>', $item_separator = '',
                                               
$item_start = '<li>', $item_end = '</li>', $force_hash = NULL, $max_items = NULL, $link_params = array() )
{
    if( !
is_null($max_items) && $max_items < 1 )
    {
        return;
    }

    if( !empty(
$items ) )
    {
        echo
$list_start;
       
$count = 0;
       
$first = true;

        foreach(
$items as $item )
        {    
// For each list item:

           
$link = resolve_link_params( $item, $force_hash, $link_params );
            if( empty(
$link ) )
            {
                continue;
            }

           
$count++;
            if(
$count>1 )
            {
                echo
$item_separator;
            }
            echo
$item_start.$link.$item_end;

            if( !
is_null($max_items) && $count >= $max_items )
            {
                break;
            }
        }
        echo
$list_end;
    }
}


/**
 * Credits stuff.
 */
function display_param_link( $params )
{
    echo
resolve_link_params( $params );
}


/**
 * Resolve a link based on params (credits stuff)
 *
 * @param array
 * @param integer
 * @param array
 * @return string
 */
function resolve_link_params( $item, $force_hash = NULL, $params = array() )
{
    global
$current_locale;

   
// echo 'resolve link ';

   
if( is_array( $item ) )
    {
        if( isset(
$item[0] ) )
        {    
// Older format, which displays the same thing for all locales:
           
return generate_link_from_params( $item, $params );
        }
        else
        {    
// First get the right locale:
            // echo $current_locale;
           
foreach( $item as $l_locale => $loc_item )
            {
                if(
$l_locale == substr( $current_locale, 0, strlen($l_locale) ) )
                {    
// We found a matching locale:
                    //echo "[$l_locale/$current_locale]";
                   
if( is_array( $loc_item[0] ) )
                    {    
// Randomize:
                       
$loc_item = hash_link_params( $loc_item, $force_hash );
                    }

                    return
generate_link_from_params( $loc_item, $params );
                }
            }
           
// No match found!
           
return '';
        }
    }

   
// Super old format:
   
return $item;
}


/**
 * Get a link line, based url hash combined with probability percentage in first column
 *
 * @param array of arrays
 * @param display for a specific hash key
 */
function hash_link_params( $link_array, $force_hash = NULL )
{
    global
$ReqHost, $ReqPath, $ReqURI;

    static
$hash;

    if( !
is_null($force_hash) )
    {
       
$hash = $force_hash;
    }
    elseif( !isset(
$hash) )
    {
       
$key = $ReqHost.$ReqPath;

        global
$Collection, $Blog;
        if( !empty(
$Blog) && strpos( $Blog->get_setting('single_links'), 'param_' ) === 0 )
        {    
// We are on a blog that doesn't even have clean URLs for posts
           
$key .= $ReqURI;
        }

       
$hash = 0;
        for(
$i=0; $i<strlen($key); $i++ )
        {
           
$hash += ord($key[$i]);
        }
       
$hash = $hash % 100 + 1;

       
// $hash = rand( 1, 100 );
       
global $debug, $Debuglog;
        if(
$debug )
        {
           
$Debuglog->add( 'Hash key: '.$hash, 'request' );
        }
    }
   
//    echo "[$hash] ";

   
foreach( $link_array as $link_params )
    {
       
// echo '<br>'.$hash.'-'.$link_params[ 0 ];
       
if( $hash <= $link_params[ 0 ] )
        {    
// select this link!
            // pre_dump( $link_params );
           
array_shift( $link_params );
            return
$link_params;
        }
    }
   
// somehow no match, return 1st element:
   
$link_params = $link_array[0];
   
array_shift( $link_params );
    return
$link_params;
}


/**
 * Generate a link from params (credits stuff)
 *
 * @param array
 * @param array
 */
function generate_link_from_params( $link_params, $params = array() )
{
   
$url = $link_params[0];
    if( empty(
$url ) )
    {
        return
'';
    }

   
// Make sure we are not missing any param:
   
$params = array_merge( array(
           
'type'        => 'link',
           
'img_url'     => '',
           
'img_width'   => '',
           
'img_height'  => '',
           
'title'       => '',
           
'target'      => '_blank',
           
'rel'         => 'noopener',
        ),
$params );

   
$text = $link_params[1];
    if(
is_array($text) )
    {
       
$text = hash_link_params( $text );
       
$text = $text[0];
    }
    if( empty(
$text ) )
    {
        return
'';
    }

   
$r = '<a href="'.$url.'"';

    if( !empty(
$params['target'] ) )
    {
       
$r .= ' target="'.$params['target'].'"';
    }

    if( !empty(
$params['rel'] ) )
    {
       
$r .= ' rel="'.$params['rel'].'"';
    }

    if(
$params['type'] == 'img' )
    {
        return
$r.' title="'.$params['title'].'"><img src="'.$params['img_url'].'" alt="'
                       
.$text.'" title="'.$params['title'].'" width="'.$params['img_width'].'" height="'.$params['img_height']
                        .
'" border="0" /></a>';
    }

    return
$r.'>'.$text.'</a>';
}


/**
 * Send a result as javascript
 * automatically includes any Messages ( @see Log::display() )
 * no return from function as it terminates processing
 *
 * @author Yabba
 *
 * @todo dh> Move this out into some more specific (not always included) file.
 *
 * @param array $methods javascript funtions to call with array of parameters
 *        format : 'function_name' => array( param1, parm2, param3 )
 * @param boolean $send_as_html Wrap the script into an html page with script tag; default is to send as js file
 * @param string $target prepended to function calls : blank or window.parent
 */
function send_javascript_message( $methods = array(), $send_as_html = false, $target = '' )
{
   
// lets spit out any messages
   
global $Messages, $param_input_err_messages;
   
ob_start();
   
$Messages->display();
   
$output = ob_get_clean();

   
// Initialize JavaScript params to send what field should be marked are error
   
$js_error_params = array();
    if( ! empty(
$param_input_err_messages ) && is_array( $param_input_err_messages ) )
    {
        foreach(
$param_input_err_messages as $param_name => $param_error )
        {
           
$js_error_params[] = $param_name.': \''.format_to_js( $param_error ).'\'';
        }
    }
   
$js_error_params = '{'.implode( ', ', $js_error_params ).'}';

   
// set target
   
$target = ( $target ? $target : param( 'js_target', 'string' ) );
    if(
$target )
    {    
// add trailing [dot]
       
$target = trim( $target, '.' ).'.';
    }

   
// target should be empty or window.parent.
   
if( $target && $target != 'window.parent.' )
    {
       
debug_die( 'Unexpected javascript target' );
    }

    if(
$output )
    {    
// we have some messages
       
$output = $target.'DisplayServerMessages( \''.format_to_js( $output ).'\', '.$js_error_params.' );'."\n";
    }

    if( !empty(
$methods ) )
    {    
// we have a methods to call
       
foreach( $methods as $method => $param_list )
        {    
// loop through each requested method
           
$params = array();
           
$internal_scripts = array();
            if( !
is_array( $param_list ) )
            {    
// lets make it an array
               
$param_list = array( $param_list );
            }
            foreach(
$param_list as $param )
            {    
// add each parameter to the output
               
if( is_array( $param ) )
                {    
// This is an array:
                   
$param = json_encode( $param );
                }
                elseif( !
is_numeric( $param ) )
                {    
// This is a string
                   
if( preg_match_all( '#<script(.*?)?>(.*?)</script>#is', $param, $match_scripts ) )
                    {    
// Extract internal scripts from content:
                       
$param = str_replace( $match_scripts[0], '', $param );
                        foreach(
$match_scripts[2] as $i => $internal_script_code )
                        {
                            if(
$internal_script_code === '' && strpos( $match_scripts[1][ $i ], 'src=' ) !== false )
                            {    
// Append external JS file to <head> to execute code:
                               
$internal_script_code = 'jQuery( \'head\' ).append( \''.format_to_js( $match_scripts[0][ $i ] ).'\' );';
                            }
                           
$internal_scripts[] = $internal_script_code;
                        }
                    }
                   
// Quote the string to javascript format:
                   
$param = '\''.format_to_js( $param ).'\'';
                }
               
$params[] = $param;// add param to the list
           
}
           
// Add method and parameters:
           
$output .= $target.$method.'('.implode( ',', $params ).');'."\n";
           
// Append all internal scripts from content on order to execute this properly:
           
foreach( $internal_scripts as $internal_script )
            {
               
$output .= "\n// Internal script from {$target}{$method}():\n".$internal_script;
            }
        }
    }

   
// Send the predefined cookies:
   
evo_sendcookies();

    if(
$send_as_html )
    {    
// we want to send as a html document
       
if( ! headers_sent() )
        {    
// Send headers only when they are not send yet to avoid an error:
           
headers_content_mightcache( 'text/html', 0 );        // Do NOT cache interactive communications.
       
}
        echo
'<html><head></head><body><script>'."\n";
        echo
$output;
        echo
'</script></body></html>';
    }
    else
    {    
// we want to send as js
       
if( ! headers_sent() )
        {    
// Send headers only when they are not send yet to avoid an error:
           
headers_content_mightcache( 'text/javascript', 0 );        // Do NOT cache interactive communications.
       
}
        global
$baseurl;
        if( empty(
$_SERVER['HTTP_REFERER'] ) ||
            ! (
$referer_url = parse_url( $_SERVER['HTTP_REFERER'] ) ) || empty( $referer_url['host'] ) ||
            ! (
$baseurl_info = parse_url( $baseurl ) ) || empty( $baseurl_info['host'] ) ||
            (
$baseurl_info['host'] != $referer_url['host'] ) )
        {    
// Deny request from other server:
           
echo 'alert( \''.sprintf( TS_('This action can only be performed with HTTP_REFERER = %s'), $baseurl ).' \');';
        }
        else
        {    
// Send JS content only for allowed referer:
           
echo $output;
        }
    }

    exit(
0);
}


/**
 * Basic tidy up of strings for using in JavaScript
 *
 * @author Yabba
 * @author Tblue
 *
 * @param string Unformatted raw data
 * @return string Formatted data
 */
function format_to_js( $unformatted )
{
   
// Convert the following chars:
    // \               => \\
    // '               => \'
    // \n              => \\n
    // newline         => \n
    // \r              => \\r
    // carriage return => \r
    // \t              => \\t
    // tab space       => \t
   
return addcslashes( $unformatted, "\\'\n\r\t" );
}


/**
 * Get available cort oprions for items
 *
 * @param integer|NULL Collection ID to get only enabled item types for the collection, NULL to get all item types
 * @param boolean true to enable none option
 * @param boolean true to also return custom field options, false otherwise
 * @return array key=>name or array( 'general' => array( key=>name ), 'custom' => array( key=>name ) )
 */
function get_available_sort_options( $coll_ID = NULL, $allow_none = false, $include_custom_fields = false )
{
   
$options = array();

    if(
$allow_none )
    {
// Enable none option
       
$options[''] = T_('None');
    }

   
$options = array_merge( $options, array(
       
'datestart'                => T_('Date issued (Default)'),
       
'order'                    => T_('Order (as explicitly specified)'),
       
//'datedeadline'           => T_('Deadline'),
       
'title'                    => T_('Title'),
       
'datecreated'              => T_('Date created'),
       
'datemodified'             => T_('Date last modified'),
       
'last_touched_ts'          => T_('Date last touched'),
       
'contents_last_updated_ts' => T_('Contents last updated'),
       
'urltitle'                 => T_('URL "filename"'),
       
'priority'                 => T_('Priority'),
       
'numviews'                 => T_('Number of members who have viewed the post (If tracking enabled)'),
       
'RAND'                     => T_('Random order!'),
    ) );

    if(
$include_custom_fields )
    {    
// We need to include custom fields as well:
       
global $DB;

       
$SQL = new SQL( 'Get custom fields for items sort options' );
       
$SQL->SELECT( 'DISTINCT( CONCAT( "custom_", itcf_type, "_", itcf_name ) ), itcf_name' );
       
$SQL->FROM( 'T_items__type_custom_field' );
       
$SQL->FROM_add( 'INNER JOIN T_items__type ON ityp_ID = itcf_ityp_ID' );
       
$SQL->WHERE( 'ityp_usage = "post"' );
        if( ! empty(
$coll_ID ) )
        {    
// Restrict by enabled item types for the given collection:
           
$SQL->FROM_add( 'INNER JOIN T_items__type_coll ON itc_ityp_ID = ityp_ID' );
           
$SQL->WHERE_and( 'itc_coll_ID = '.$DB->quote( $coll_ID ) );
        }
       
$custom_fields = $DB->get_assoc( $SQL );

       
// Add custom fields as available sort options:
       
return array( 'general' => $options, 'custom' => $custom_fields );
    }

    return
$options;
}


/**
 * Get available sort options for blogs
 *
 * @return array key=>name
 */
function get_coll_sort_options()
{
    return array(
       
'order'        => T_('Order (Default)'),
       
'ID'           => T_('Blog ID'),
       
'name'         => T_('Name'),
       
'shortname'    => T_('Short name'),
       
'tagline'      => T_('Tagline'),
       
'shortdesc'    => T_('Short Description'),
       
'urlname'      => T_('URL "filename"'),
       
'RAND'         => T_('Random order!'),
    );
}


/**
 * Converts array to form option list
 *
 * @param array of option values and descriptions
 * @param integer|array selected keys
 * @param array provide a choice for "none_value" with value ''
 * @return string
 */
function array_to_option_list( $array, $default = '', $allow_none = array() )
{
    if( !
is_array( $default ) )
    {
       
$default = array( $default );
    }

   
$r = '';

    if( !empty(
$allow_none) )
    {
       
$r .= '<option value="'.$allow_none['none_value'].'"';
        if( empty(
$default) ) $r .= ' selected="selected"';
       
$r .= '>'.format_to_output($allow_none['none_text']).'</option>'."\n";
    }

    foreach(
$array as $k=>$v )
    {
       
$r .=  '<option value="'.format_to_output($k,'formvalue').'"';
        if(
in_array( $k, $default ) ) $r .= ' selected="selected"';
       
$r .= '>';
       
$r .= format_to_output( $v, 'htmlbody' );
       
$r .=  '</option>'."\n";
    }

    return
$r;
}


/**
 * Get a value from a volatile/lossy cache.
 *
 * @param string key
 * @param boolean success (by reference)
 * @return mixed True in case of success, false in case of failure. NULL, if no backend is available.
 */
function get_from_mem_cache( $key, & $success )
{
    global
$Timer;

   
$Timer->resume( 'get_from_mem_cache', false );

    if(
function_exists( 'apcu_fetch' ) )
    {    
// APCu
       
$r = apcu_fetch( $key, $success );
    }
    elseif(
function_exists( 'apc_fetch' ) )
    {    
// APC
       
$r = apc_fetch( $key, $success );
    }
    elseif(
function_exists( 'xcache_get' ) && ini_get( 'xcache.var_size' ) > 0 )
    {    
// XCache
       
$r = xcache_get( $key );
    }

    if( ! isset(
$success) )
    {
// set $success for implementation that do not set it itself (only APC does so)
       
$success = isset($r);
    }
    if( !
$success )
    {
       
$r = NULL;

        global
$Debuglog;
       
$Debuglog->add( 'No caching backend available for reading "'.$key.'".', 'cache' );
    }

   
$Timer->pause( 'get_from_mem_cache', false );

    return
$r;
}


/**
 * Set a value to a volatile/lossy cache.
 *
 * There's no guarantee that the data is still available, since e.g. old
 * values might get purged.
 *
 * @param string key
 * @param mixed Data. Objects would have to be serialized.
 * @param int Time to live (seconds). Default is 0 and means "forever".
 * @return mixed
 */
function set_to_mem_cache( $key, $payload, $ttl = 0 )
{
    global
$Timer;

   
$Timer->resume( 'set_to_mem_cache', false );

    if(
function_exists( 'apcu_store' ) )
    {    
// APCu
       
$r = apcu_store( $key, $payload, $ttl );
    }
    elseif(
function_exists( 'apc_store' ) )
    {    
// APC
       
$r = apc_store( $key, $payload, $ttl );
    }
    elseif(
function_exists( 'xcache_set' ) && ini_get( 'xcache.var_size' ) > 0 )
    {    
// XCache
       
$r = xcache_set( $key, $payload, $ttl );
    }
    else
    {    
// No available cache module:
       
global $Debuglog;
       
$Debuglog->add( 'No caching backend available for writing "'.$key.'".', 'cache' );
       
$r = NULL;
    }

   
$Timer->pause( 'set_to_mem_cache', false );

    return
$r;
}


/**
 * Remove a given key from the volatile/lossy cache.
 *
 * @param string key
 * @return boolean True on success, false on failure. NULL if no backend available.
 */
function unset_from_mem_cache( $key )
{
    if(
function_exists( 'apc_delete' ) )
    {    
// APC
       
return apc_delete( $key );
    }

    if(
function_exists( 'xcache_unset' ) )
    {    
// XCache
       
return xcache_unset( gen_key_for_cache( $key ) );
    }

    if(
function_exists( 'apcu_delete' ) )
    {    
// APCu
       
return apcu_delete( $key );
    }
}


/**
 * Generate order by clause
 *
 * @param string The order values are separated by space or comma
 * @param string An order direction: ASC, DESC
 * @param string DB prefix
 * @param string ID field name with prefix
 * @param array Names of DB fields(without prefix) that are available
 * @return string The order fields are separated by comma
 */
function gen_order_clause( $order_by, $order_dir, $dbprefix, $dbIDname_disambiguation, $available_fields = NULL )
{
   
$order_by = str_replace( ' ', ',', $order_by );
   
$orderby_array = explode( ',', $order_by );

   
$order_dir = explode( ',', str_replace( ' ', ',', $order_dir ) );

    if(
is_array( $available_fields ) )
    {
// Exclude the incorrect fields from order clause
       
foreach( $orderby_array as $i => $orderby_field )
        {
            if( !
in_array( $orderby_field, $available_fields ) )
            {
                unset(
$orderby_array[ $i ] );
            }
        }
    }

   
// Format each order param with default column names:
   
foreach( $orderby_array as $i => $orderby_value )
    {
// If the order_by field contains a '.' character which is a table separator we must not use the prefix ( E.g. temp_table.value )
       
$use_dbprefix = ( strpos( $orderby_value, '.' ) !== false ) ? '' : $dbprefix;
       
$orderby_array[ $i ] = $use_dbprefix.$orderby_value.' '.( isset( $order_dir[ $i ] ) ? $order_dir[ $i ] : $order_dir[0] );
    }

   
// Add an ID parameter to make sure there is no ambiguity in ordering on similar items:
   
$orderby_array[] = $dbIDname_disambiguation.' '.$order_dir[0];

   
$order_by = implode( ', ', $orderby_array );

   
// Special case for RAND:
   
$order_by = str_replace( $dbprefix.'RAND ', 'RAND() ', $order_by );

    return
$order_by;
}


/**
 * Get the IconLegend instance.
 *
 * @return IconLegend or false, if the user has not set "display_icon_legend"
 */
function & get_IconLegend()
{
    static
$IconLegend;

    if( ! isset(
$IconLegend) )
    {
        global
$UserSettings;
        if(
$UserSettings->get('display_icon_legend') )
        {
           
/**
             * Icon Legend
             */
           
load_class( '_core/ui/_iconlegend.class.php', 'IconLegend' );
           
$IconLegend = new IconLegend();
        }
        else
        {
           
$IconLegend = false;
        }
    }
    return
$IconLegend;
}


/**
 * Get name of active opcode cache, or "none".
 * {@internal Anyone using something else, please extend.}}
 * @return string
 */
function get_active_opcode_cache()
{
    if(
function_exists('opcache_invalidate') )
    {
        return
'OPCache';
    }

    if(
function_exists('apc_delete_file') )
    {
        return
'APC';
    }

   
// xcache: xcache.var_size must be > 0. xcache_set is not necessary (might have been disabled).
   
if( ini_get('xcache.size') > 0 )
    {
        return
'xcache';
    }

    if(
ini_get('eaccelerator.enable') )
    {
       
$eac_info = eaccelerator_info();
        if(
$eac_info['cache'] )
        {
            return
'eAccelerator';
        }
    }

    return
'none';
}


/**
 * Get name of active user cache, or "none".
 * {@internal Anyone using something else, please extend.}}
 * @return string
 */
function get_active_user_cache()
{
    if(
function_exists( 'apcu_cache_info' ) /* && ini_get( 'apc.enabled' ) */ )
    {
        return
'APCu';
    }

    if(
function_exists( 'apc_cache_info' ) /* && ini_get( 'apc.enabled' ) */ )
    {
        return
'APC';
    }

   
// xcache: xcache.var_size must be > 0. xcache_set is not necessary (might have been disabled).
   
if( ini_get('xcache.size') > 0 )
    {
        return
'xcache';
    }

    return
'none';
}


/**
 * Invalidate all page caches.
 * This function should be processed every time, when some users or global settings was modified,
 * and this modification has an imortant influence for the front office display.
 * Modifications that requires to invalidate all page caches:
 *   - installing/removing/reloading/enabling/disabling plugins
 *   - editing user settings like allow profile pics, new users can register, user settings>display
 */
function invalidate_pagecaches()
{
    global
$DB, $Settings, $servertimenow;

   
// get current server time
   
$timestamp = ( empty( $servertimenow ) ? time() : $servertimenow );

   
// get all blog ids
   
if( $blog_ids = $DB->get_col( 'SELECT blog_ID FROM T_blogs' ) )
    {    
// build invalidate query
       
$query = 'REPLACE INTO T_coll_settings ( cset_coll_ID, cset_name, cset_value ) VALUES';
        foreach(
$blog_ids as $blog_id )
        {
           
$query .= ' ('.$blog_id.', "last_invalidation_timestamp", '.$timestamp.' ),';
        }
       
$query = substr( $query, 0, strlen( $query ) - 1 );
       
$DB->query( $query, 'Invalidate blogs\'s page caches' );
    }

   
// Invalidate general cache content also
   
$Settings->set( 'last_invalidation_timestamp', $timestamp );
   
$Settings->dbupdate();
}


/**
* Get $ReqPath, $ReqURI
*
* @return array ($ReqPath,$ReqURI);
*/
function get_ReqURI()
{
    global
$Debuglog;

   
// Investigation for following code by Isaac - http://isaacschlueter.com/
   
if( isset($_SERVER['REQUEST_URI']) && !empty($_SERVER['REQUEST_URI']) )
    {
// Warning: on some IIS installs it it set but empty!
       
$Debuglog->add( 'vars: vars: Getting ReqURI from REQUEST_URI', 'request' );
       
$ReqURI = $_SERVER['REQUEST_URI'];

       
// Build requested Path without query string:
       
$pos = strpos( $ReqURI, '?' );
        if(
false !== $pos )
        {
           
$ReqPath = substr( $ReqURI, 0, $pos  );
        }
        else
        {
           
$ReqPath = $ReqURI;
        }
    }
    elseif( isset(
$_SERVER['URL']) )
    {
// ISAPI
       
$Debuglog->add( 'vars: Getting ReqPath from URL', 'request' );
       
$ReqPath = $_SERVER['URL'];
       
$ReqURI = isset($_SERVER['QUERY_STRING']) && !empty( $_SERVER['QUERY_STRING'] ) ? ($ReqPath.'?'.$_SERVER['QUERY_STRING']) : $ReqPath;
    }
    elseif( isset(
$_SERVER['PATH_INFO']) )
    {
// CGI/FastCGI
       
if( isset($_SERVER['SCRIPT_NAME']) )
        {
           
$Debuglog->add( 'vars: Getting ReqPath from PATH_INFO and SCRIPT_NAME', 'request' );

            if (
$_SERVER['SCRIPT_NAME'] == $_SERVER['PATH_INFO'] )
            {    
/* both the same so just use one of them
                 * this happens on a windoze 2003 box
                 * gotta love microdoft
                 */
               
$Debuglog->add( 'vars: PATH_INFO and SCRIPT_NAME are the same', 'request' );
               
$Debuglog->add( 'vars: Getting ReqPath from PATH_INFO only instead', 'request' );
               
$ReqPath = $_SERVER['PATH_INFO'];
            }
            else
            {
               
$ReqPath = $_SERVER['SCRIPT_NAME'].$_SERVER['PATH_INFO'];
            }
        }
        else
        {
// does this happen??
           
$Debuglog->add( 'vars: Getting ReqPath from PATH_INFO only!', 'request' );

           
$ReqPath = $_SERVER['PATH_INFO'];
        }
       
$ReqURI = isset($_SERVER['QUERY_STRING']) && !empty( $_SERVER['QUERY_STRING'] ) ? ($ReqPath.'?'.$_SERVER['QUERY_STRING']) : $ReqPath;
    }
    elseif( isset(
$_SERVER['ORIG_PATH_INFO']) )
    {
// Tomcat 5.5.x with Herbelin PHP servlet and PHP 5.1
       
$Debuglog->add( 'vars: Getting ReqPath from ORIG_PATH_INFO', 'request' );
       
$ReqPath = $_SERVER['ORIG_PATH_INFO'];
       
$ReqURI = isset($_SERVER['QUERY_STRING']) && !empty( $_SERVER['QUERY_STRING'] ) ? ($ReqPath.'?'.$_SERVER['QUERY_STRING']) : $ReqPath;
    }
    elseif( isset(
$_SERVER['SCRIPT_NAME']) )
    {
// Some Odd Win2k Stuff
       
$Debuglog->add( 'vars: Getting ReqPath from SCRIPT_NAME', 'request' );
       
$ReqPath = $_SERVER['SCRIPT_NAME'];
       
$ReqURI = isset($_SERVER['QUERY_STRING']) && !empty( $_SERVER['QUERY_STRING'] ) ? ($ReqPath.'?'.$_SERVER['QUERY_STRING']) : $ReqPath;
    }
    elseif( isset(
$_SERVER['PHP_SELF']) )
    {
// The Old Stand-By
       
$Debuglog->add( 'vars: Getting ReqPath from PHP_SELF', 'request' );
       
$ReqPath = $_SERVER['PHP_SELF'];
       
$ReqURI = isset($_SERVER['QUERY_STRING']) && !empty( $_SERVER['QUERY_STRING'] ) ? ($ReqPath.'?'.$_SERVER['QUERY_STRING']) : $ReqPath;
    }
    else
    {
       
$ReqPath = false;
       
$ReqURI = false;
       
?>
       <p class="error">
        Warning: $ReqPath could not be set. Probably an odd IIS problem.
        </p>
        <p>
        Go to your <a href="<?php echo $baseurl.$install_subdir ?>phpinfo.php">phpinfo page</a>,
        look for occurences of <code><?php
       
// take the baseurlroot out..
       
echo preg_replace('#^'.preg_quote( $baseurlroot, '#' ).'#', '', $baseurl.$install_subdir )
       
?>phpinfo.php</code> and copy all lines
        containing this to the <a href="http://forums.b2evolution.net">forum</a>. Also specify what webserver
        you're running on.
        <br />
        (If you have deleted your install folder &ndash; what is recommended after successful setup &ndash;
        you have to upload it again before doing this).
        </p>
        <?php
   
}

   
$r = array( $ReqPath, $ReqURI );

   
// Format several danger chars to urlencoded format to avoid issues:
   
$r = str_replace( array( '"', '\'', '<', '>' ), array( '%22', '%27', '%3C', '%3E' ), $r );

    return
$r;
}


/**
 * Get URL to REST API script depending on current collection base url from front-office or site base url from back-office
 *
 * Note: For back-office or no collection page _init_hit.inc.php should be called before this call, because ReqHost and ReqPath must be initialized
 *
 * @return string URL to htsrv folder
 */
function get_restapi_url()
{
    global
$restapi_script;

    return
get_htsrv_url().$restapi_script;
}


/**
 * Force URL from http to https protocol
 *
 * @param string Original URL
 * @param boolean|string TRUE to force without settings checking,
 *                       FALSE to keep original URL without forcing,
 *                       'login' - Force only when it is enabled by setting "Require SSL"
 * @return string Forced URL
 */
function force_https_url( $url, $force_https = true )
{
    if(
$force_https === 'login' )
    {    
// Force url to use https if it is defiend in the setting "Require SSL":
       
global $Settings;
       
$force_https = (boolean)$Settings->get( 'require_ssl' );
    }

    if(
$force_https === true )
    {    
// Force URL only when it is requested by param or enabled by setting checking above:
       
$url = preg_replace( '#^http://#i', 'https://', $url );
    }

    return
$url;
}


/**
 * Check and redirect if the requested URL must be used as https instead of http
 *
 * @param boolean|string TRUE to force without settings checking,
 *                       FALSE to keep original URL without forcing,
 *                       'login' - Force only when it is enabled by setting "Require SSL"
 * @param string Original URL, NULL - to use current URL = $ReqURL
 */
function check_https_url( $force_https = true, $url = NULL )
{
    if(
$url === NULL )
    {    
// Use current URL by default:
       
global $ReqURL;
        if( empty(
$ReqURL ) )
        {    
// If this URL is not defined yet:
           
return;
        }
       
$url = $ReqURL;
    }

   
// Try to force the requested URL:
   
$forced_url = force_https_url( $url, $force_https );

    if(
$forced_url != $url )
    {    
// If the requested is wrong then redirect to correct what must be used instead:
       
header_redirect( $forced_url );
    }
}


/**
 * Get URL to htsrv folder depending on current collection base url from front-office or site base url from back-office
 *
 * Note: For back-office or no collection page _init_hit.inc.php should be called before this call, because ReqHost and ReqPath must be initialized
 *
 * @param boolean TRUE to use https URL
 * @return string URL to htsrv folder
 */
function get_htsrv_url( $force_https = false )
{
    global
$Collection, $Blog;

    if(
is_admin_page() || empty( $Blog ) )
    {    
// For back-office or when no collection page:
       
return get_samedomain_htsrv_url( $force_https );
    }
    else
    {    
// For current collection:
       
return $Blog->get_local_htsrv_url( NULL, $force_https );
    }
}


/**
 * Get htsrv url on the same domain as the http request came from
 *
 * Note: _init_hit.inc.php should be called before this call, because ReqHost and ReqPath must be initialized
 *
 * @param boolean|string TRUE to use https URL
 * @return string URL to htsrv folder
 */
function get_samedomain_htsrv_url( $force_https = false )
{
    global
$ReqHost, $ReqPath, $baseurl, $htsrv_url, $htsrv_subdir, $Collection, $Blog, $is_cli;

   
// Force URL if it is required and enabled by settings:
   
$req_htsrv_url = force_https_url( $htsrv_url, $force_https );

   
// Cut htsrv folder from end of the URL:
   
$req_htsrv_url = substr( $req_htsrv_url, 0, strlen( $req_htsrv_url ) - strlen( $htsrv_subdir ) );

    if(
$is_cli || empty( $ReqHost ) || strpos( $ReqHost.$ReqPath, $req_htsrv_url ) !== false )
    {    
// If current request path contains the required htsrv URL
        // or $ReqHost is not initialized e-g on install
        // or this is CLI mode where $ReqHost is not defined:
       
return $req_htsrv_url.$htsrv_subdir;
    }

   
$baseurl_parts = @parse_url( $baseurl );
   
$req_url_parts = @parse_url( $ReqHost );
   
$htsrv_url_parts = @parse_url( $req_htsrv_url );
    if( ( ! isset(
$baseurl_parts['host'] ) ) ||
        ( ! isset(
$req_url_parts['host'] ) ) ||
        ( ! isset(
$htsrv_url_parts['host'] ) ) )
    {
       
debug_die( 'Invalid hosts!' );
    }

    if(
$req_url_parts['host'] == $baseurl_parts['host'] &&
        ! isset(
$req_url_parts['path'] ) &&
        isset(
$baseurl_parts['path'] ) )
    {    
// Don't miss folder of base url from url like http://site.com/folder/:
       
$req_url_parts['path'] = $baseurl_parts['path'];
    }

   
$req_domain = rtrim( $req_url_parts['host'].( isset( $req_url_parts['path'] ) ? $req_url_parts['path'] : '' ), '/' );
   
$htsrv_domain = rtrim( $htsrv_url_parts['host'].( isset( $htsrv_url_parts['path'] ) ? $htsrv_url_parts['path'] : '' ), '/' );

   
// Replace domain + path of htsrv URL with current request:
   
$samedomain_htsrv_url = substr_replace( $req_htsrv_url, $req_domain, strpos( $req_htsrv_url, $htsrv_domain ), strlen( $htsrv_domain ) );

   
// Revert htsrv folder to end of the URL which has been cut above:
   
$samedomain_htsrv_url .= $htsrv_subdir;

   
// fp> The following check would apply well if we always had 301 redirects.
    // But it's possible to turn them off in SEO settings for some page and not others (we don't know which here)
  // And some kinds of pages do not have 301 redirections implemented yet, e-g: disp=users
  /*
    if( ( !is_admin_page() ) && ( !empty( $Blog ) ) && ( $samedomain_htsrv_url != $Blog->get_htsrv_url( $secure ) ) )
    {
        debug_die( 'The blog is configured to have /htsrv/ at:<br> '.$Blog->get_htsrv_url( $secure ).'<br>but in order to stay on the current domain, we would need to use:<br>'.$samedomain_htsrv_url.'<br>Maybe we have a missing redirection to the proper blog url?' );
    }
    */

   
return $samedomain_htsrv_url;
}


/**
 * Set max execution time
 *
 * @param integer seconds
 * @return string the old value on success, false on failure.
 */
function set_max_execution_time( $seconds )
{
    if(
function_exists( 'set_time_limit' ) )
    {
       
set_time_limit( $seconds );
    }
    return @
ini_set( 'max_execution_time', $seconds );
}


/**
 * Sanitize a comma-separated list of numbers (IDs)
 *
 * @param string
 * @param bool Return array if true, string otherwise
 * @param bool Quote each element (for use in SQL queries)
 * @return string
 */
function sanitize_id_list( $str, $return_array = false, $quote = false )
{
    if(
is_null($str) )
    {    
// Allow NULL values
       
$str = '';
    }

   
// Explode and trim
   
$array = array_map( 'trim', explode(',', $str) );

   
// Convert to integer and remove all empty values
   
$array = array_filter( array_map('intval', $array) );

    if( !
$return_array && $quote )
    {    
// Quote each element and return a string
       
global $DB;
        return
$DB->quote($array);
    }
    return (
$return_array ? $array : implode(',', $array) );
}


/**
 * A wrapper for json_encode function
 * We need to pass valid UTF-8 string to json_encode, otherwise it may return NULL
 *
 * @param mixed
 * @return string
 */
function evo_json_encode( $a = false )
{
    if(
is_string( $a ) )
    {
// Convert to UTF-8
       
$a = current_charset_to_utf8( $a );
    }
    elseif(
is_array( $a ) )
    {
// Recursively convert to UTF-8
       
array_walk_recursive( $a, 'current_charset_to_utf8' );
    }

   
$result = json_encode( $a );
    if(
$result === false )
    {
// If json_encode returns FALSE because of some error we should set correct json empty value as '[]' instead of false
       
$result = '[]';
    }

    return
$result;
}


/**
 * A helper function to conditionally convert a string from current charset to UTF-8
 *
 * @param string
 * @return string
 */
function current_charset_to_utf8( & $a )
{
    global
$current_charset;

    if(
is_string( $a ) && $current_charset != '' && $current_charset != 'utf-8' )
    {
// Convert string to utf-8 if it has another charset
       
$a = convert_charset( $a, 'utf-8', $current_charset );
    }

    return
$a;
}


if( !
function_exists( 'property_exists' ) )
{
   
/**
     * Create property_exists function if it does not exist ( PHP < 5.1 )
     * @param object
     * @param string
     *
     * @return bool
     */
   
function property_exists( $class, $property )
    {
        if(
is_object( $class ) )
        {
           
$vars = get_object_vars( $class );
        }
        else
        {
           
$vars = get_class_vars( $class );
        }
        return
array_key_exists( $property, $vars );
    }
}


// fp>vitaliy: move to a file that is not included everywhere!
/**
 * Update global $http_response_code and call function header()
 *
 * NOTICE: When you start to use new code please add it to the hits filter "HTTP resp"
 *         in the file "/inc/sessions/views/_stats_view.funcs.php",
 *         function filter_hits(), array $resp_codes
 *
 * @param string Header
 * @param integer Header response code
 */
function header_http_response( $string, $code = NULL )
{
    global
$http_response_code;

   
$string = 'HTTP/1.1 '. $string;

    if(
is_null( $code ) )
    {
        if(
preg_match( '/(\d{3})/', $string, $matches ) )
        {
           
$http_response_code = (int)$matches[0];
        }
    }
    else
    {
       
$http_response_code = $code;
    }

   
header( $string );
}


/**
 * Add a trailing slash, if none present
 *
 * @param string the path/url
 * @return string the path/url with trailing slash
 */
function trailing_slash( $path )
{
    if( empty(
$path) || utf8_substr( $path, -1 ) == '/' )
    {
        return
$path;
    }
    else
    {
        return
$path.'/';
    }
}


/**
 * Remove trailing slash, if present
 *
 * @param string the path/url
 * @return string the path/url without trailing slash
 */
function no_trailing_slash( $path )
{
    if(
utf8_substr( $path, -1 ) == '/' )
    {
        return
utf8_substr( $path, 0, utf8_strlen( $path )-1 );
    }
    else
    {
        return
$path;
    }
}


/**
 * Convert integer to IP address
 *
 * @param integer Number
 * @return string IP address
 */
function int2ip( $int )
{
   
$ip = array();
   
$ip[0] = (int) ( $int / 256 / 256 / 256 );
   
$ip[1] = (int) ( ( $int - ( $ip[0] * 256 * 256 * 256 ) ) / 256 / 256 );
   
$ip[2] = (int) ( ( $int - ( $ip[0] * 256 * 256 * 256 ) - ( $ip[1] * 256 * 256 ) ) / 256 );
   
$ip[3] = $int - ( $ip[0] * 256 * 256 * 256 ) - ( $ip[1] * 256 * 256 ) - ( $ip[2] * 256 );

    return
$ip[0].'.'.$ip[1].'.'.$ip[2].'.'.$ip[3];
}


/**
 * Check if the given string is a valid IPv4 or IPv6 address value
 *
 * @param string IP
 * @return boolean true if valid, false otherwise
 */
function is_valid_ip_format( $ip )
{
    return
filter_var( $ip, FILTER_VALIDATE_IP ) !== false;
}




/**
 * Convert IP address to integer (get only 32bits of IPv6 address)
 *
 * @param string IP address
 * @return integer Number
 */
function ip2int( $ip )
{
    if( !
is_valid_ip_format( $ip ) )
    {
// IP format is incorrect
       
return 0;
    }

    if(
$ip == '::1' )
    {    
// Reserved IP for localhost
       
$ip = '127.0.0.1';
    }

   
$parts = unpack( 'N*', inet_pton( $ip ) );
   
// In case of IPv6 return only a parts of it
   
$result = ( strpos( $ip, '.' ) !== false ) ? $parts[1] /* IPv4*/ : $parts[4] /* IPv6*/;

    if(
$result < 0 )
    {
// convert unsigned int to signed from unpack.
        // this should be OK as it will be a PHP float not an int
       
$result += 4294967296;
    }

    return
$result;
}


/**
 * Check if URL has a domain in IP format
 *
 * @param string URL
 * @return boolean
 */
function is_ip_url_domain( $url )
{
   
$url_data = parse_url( $url );

    if(
$url_data === false || ! isset( $url_data['host'] ) )
    {    
// Wrong url:
       
return false;
    }

   
// Check if host is IP address:
   
return is_valid_ip_format( $url_data['host'] );
}


/**
 * Save text data to file, create target file if it doesn't exist
 *
 * @param string data to be written
 * @param string filename (full path to a file)
 * @param string fopen mode
 */
function save_to_file( $data, $filename, $mode = 'a' )
{
    global
$Settings, $evo_save_file_error_msg;

    if( !
file_exists( $filename ) )
    {    
// Try to create a target file:
       
if( ! @touch( $filename ) )
        {    
// If file could not be created:
           
$evo_save_file_error_msg = T_('File could not be created!');
            return
false;
        }

       
// Doesn't work during installation
       
if( ! empty( $Settings ) )
        {
           
$chmod = $Settings->get( 'fm_default_chmod_file' );
            @
chmod( $filename, octdec( $chmod ) );
        }
    }

    if( !
is_writable( $filename ) )
    {    
// File is not writable:
       
$evo_save_file_error_msg = T_('File is not writable!');
        return
false;
    }

    if( ! (
$f = @fopen( $filename, $mode ) ) )
    {    
// Could not open file:
       
$evo_save_file_error_msg = T_('File could not be opened for writing data!');
        return
false;
    }
    if( ! @
fwrite( $f, $data ) )
    {    
// Could not write data into file:
       
$evo_save_file_error_msg = T_('Data could not be written to the file!');
        return
false;
    }
    @
fclose( $f );

    if( !
file_exists( $filename ) )
    {    
// Additonal check for existing file on disk:
       
$evo_save_file_error_msg = T_('File doesn\'t exist!');
        return
false;
    }

   
// Reset error log on success result:
   
$evo_save_file_error_msg = '';

   
// Return file name on success result:
   
return $filename;
}


/**
 * Check if current request is AJAX
 *
 * @return boolean TRUE/FALSE
 */
function is_ajax_request()
{
    global
$is_ajax_request;

    return isset(
$is_ajax_request ) && $is_ajax_request === true;
}


/**
 * Check if current request is AJAX content
 * Used in order to get only content of the requested page
 *
 * @param string Template name
 * @return boolean TRUE/FALSE
 */
function is_ajax_content( $template_name = '' )
{
    global
$ajax_content_mode;

   
// Template names of content: @see skin_include()
   
$content_templates = array( '$disp$', '_item_block.inc.php', '_item_content.inc.php' );

    return !empty(
$ajax_content_mode ) &&
       
$ajax_content_mode === true &&
        !
in_array( $template_name, $content_templates );
}


/**
 * Add a message to AJAX Log
 *
 * @param string Message
 * @param string|array Category, default is to use the object's default category.
 *        Can also be an array of categories to add the same message to.
 */
function ajax_log_add( $message, $category = NULL )
{
    global
$ajax_Log;

    if( ! isset(
$ajax_Log ) ||
        ! (
$ajax_Log instanceof Log ) )
    {    
// AJAX Log is not initialized:
       
return;
    }

   
// Add a message to AJAX Log:
   
$ajax_Log->add( $message, $category );
}


/**
 * Display AJAX log
 */
function ajax_log_display()
{
    global
$ajax_Log, $debug, $debug_jslog, $current_debug, $current_debug_jslog;

    if( ! (
$debug || $debug_jslog || $current_debug || $current_debug_jslog ) )
    {    
// At least one debug must be enabled:
       
return;
    }

    if( ! isset(
$ajax_Log ) ||
        ! (
$ajax_Log instanceof Log ) )
    {    
// AJAX Log is not initialized:
       
return;
    }

   
// Print out AJAX Log messages:
   
$ajax_Log->display( NULL, NULL, true, 'all',
        array(
               
'error' => array( 'class' => 'jslog_error', 'divClass' => false ),
               
'note'  => array( 'class' => 'jslog_note',  'divClass' => false ),
            ),
'ul', 'jslog' );
}


/**
 * Insert system log into DB
 *
 * @param string Message text
 * @param string Log type: 'info', 'warning', 'error', 'critical_error'
 * @param string Object type: 'comment', 'item', 'user', 'file', 'email_log' or leave default NULL if none of them
 * @param integer Object ID
 * @param string Origin type: 'core', 'plugin'
 * @param integer Origin ID
 * @param integer User ID
 */
function syslog_insert( $message, $log_type, $object_type = NULL, $object_ID = NULL, $origin_type = 'core', $origin_ID = NULL, $user_ID = NULL )
{
    global
$servertimenow;

   
$Syslog = new Syslog();
   
$Syslog->set_user( $user_ID );
   
$Syslog->set( 'type', $log_type );
   
$Syslog->set_origin( $origin_type, $origin_ID );
   
$Syslog->set_object( $object_type, $object_ID );
   
$Syslog->set_message( $message );
   
$Syslog->set( 'timestamp', date2mysql( $servertimenow ) );
   
$Syslog->dbinsert();
}


/**
 * Get a param to know where script is calling now, Used for JS functions
 *
 * @return string
 */
function request_from()
{
    global
$request_from;

    if( !empty(
$request_from ) )
    {
// AJAX request
       
return $request_from;
    }

    if(
is_admin_page() )
    {
// Backoffice
       
global $ctrl;
        return !empty(
$ctrl ) ? $ctrl : 'admin';
    }
    else
    {
// Frontoffice
       
return 'front';
    }
}


/**
 * Get an error message text about file permissions
 */
function get_file_permissions_message()
{
    return
sprintf( T_( '(Please check UNIX file permissions on the parent folder. %s)' ), get_manual_link( 'file-permissions' ) );
}


/**
 * Flush the output buffer
 */
function evo_flush()
{
    global
$Timer, $disable_evo_flush;

    if( isset(
$disable_evo_flush ) && $disable_evo_flush )
    {    
// This function is disabled (for example, used for ajax/rest api requests)
       
return;
    }

   
// To fill the output buffer in order to flush data:
    // NOTE: Uncomment the below line if flush doesn't work as expected
    // echo str_repeat( ' ', 4096 );

    // New line char is required as flag of that output portion is printed out:
   
echo "\n";

   
$zlib_output_compression = ini_get( 'zlib.output_compression' );
    if( empty(
$zlib_output_compression ) || $zlib_output_compression == 'Off' )
    {
// This function helps to turn off output buffering
        // But do NOT use it when zlib.output_compression is ON, because it creates the die errors

        // fp/yura TODO: we need to optimize this: We want to flush to screen and continue caching.
        //               This needs investigation and checking other similar places.
       
global $PageCache;
        if( ! ( isset(
$PageCache ) && ! empty( $PageCache->is_collecting ) ) )
        {
// Only when page cache is not running now because a notice error can appears in function PageCache->end_collect()
           
@ob_end_flush();
        }
    }
   
flush();

    if( isset(
$Timer ) && $Timer->get_state( 'first_flush' ) == 'running' )
    {
// The first fulsh() was called, stop the timer
       
$Timer->pause( 'first_flush' );
    }
}

// ---------- APM : Application Performance Monitoring -----------

/**
 * Name the transaction for the APM.
 * This avoids that every request be called 'index.php' or 'evoadm.php' or 'cron_exec.php'
 *
 * @param mixed $request_transaction_name
 */
function apm_name_transaction( $request_transaction_name )
{
    if(
extension_loaded('newrelic'))
    {    
// New Relic is installed on the server for monitoring.
       
newrelic_name_transaction( $request_transaction_name );
    }
}

/**
 * Log a custom metric
 *
 * @param mixed $name name of the custom metric
 * @param mixed $value assumed to be in milliseconds (ms)
 */
function apm_log_custom_metric( $name, $value )
{
    if(
extension_loaded('newrelic'))
    {    
// New Relic is installed on the server for monitoring.
       
newrelic_custom_metric( 'Custom/'.$name, $value );
    }
}

/**
 * Log a custom param
 *
 * @param mixed $name name of the custom param
 * @param mixed $value of the custom param
 */
function apm_log_custom_param( $name, $value )
{
    if(
extension_loaded('newrelic'))
    {    
// New Relic is installed on the server for monitoring.
       
newrelic_add_custom_parameter( $name, $value );
    }
}


/**
 * Echo JavaScript to edit values of column in the table list
 *
 * @param array Params
 */
function echo_editable_column_js( $params = array() )
{
   
$params = array_merge( array(
           
'column_selector' => '', // jQuery selector of cell
           
'ajax_url'        => '', // AJAX url to update a column value
           
'options'         => array(), // Key = Value of option, Value = Title of option. Do not use Javascript code to populate this - use 'options_eval' param to do this.
           
'new_field_name'  => '', // Name of _POST variable that will be send to ajax request with new value
           
'ID_value'        => '', // jQuery to get value of ID
           
'ID_name'         => '', // ID of field in DB
           
'tooltip'         => T_('Click to edit'),
           
'colored_cells'   => false, // Use TRUE when colors are used for background of cell
           
'print_init_tags' => true, // Use FALSE to don't print <script> tags if it is already used inside js
           
'field_type'      => 'select', // Type of the editable field: 'select', 'text'
           
'field_class'     => '', // Class of the editable field
           
'null_text'       => '', // Null text of an input field, Use TS_() to translate it
           
'callback_code'   => '', // Additional JS code after main callback code
       
), $params );

   
expose_var_to_js( $params['column_selector'], $params, 'evo_init_editable_column_config' );
}


/**
 * Get a button class name depending on template
 *
 * @param string Type: 'button', 'button_text', 'button_group'
 * @param string TRUE - to get class value for jQuery selector
 * @return string Class name
 */
function button_class( $type = 'button', $jQuery_selector = false )
{
   
// Default class names
   
$classes = array(
           
'button'       => 'roundbutton', // Simple button with icon
           
'button_red'   => 'roundbutton_red', // Button with red background
           
'button_green' => 'roundbutton_green', // Button with green background
           
'text'         => 'roundbutton_text', // Button with text
           
'text_primary' => 'roundbutton_text', // Button with text with special style color
           
'text_success' => 'roundbutton_text', // Button with text with special style color
           
'text_danger'  => 'roundbutton_text', // Button with text with special style color
           
'group'        => 'roundbutton_group', // Group of the buttons
       
);

    if(
is_admin_page() )
    {
// Some admin skins may have special class names
       
global $AdminUI;
        if( ! empty(
$AdminUI ) )
        {
           
$template_classes = $AdminUI->get_template( 'button_classes' );
        }
    }
    else
    {
// Some front end skins may have special class names
       
global $Skin;
        if( ! empty(
$Skin ) )
        {
           
$template_classes = $Skin->get_template( 'button_classes' );
        }
    }
    if( !empty(
$template_classes ) )
    {
// Get class names from admin template
       
$classes = array_merge( $classes, $template_classes );
    }

   
$class_name = isset( $classes[ $type ] ) ? $classes[ $type ] : '';

    if(
$jQuery_selector && ! empty( $class_name ) )
    {
// Convert class name to jQuery selector
       
$class_name = '.'.str_replace( ' ', '.', $class_name );
    }

    return
$class_name;
}


/**
 * Initialize JavaScript to build and open window
 */
function echo_modalwindow_js()
{
    global
$AdminUI, $Collection, $Blog, $modal_window_js_initialized;

    if( ! empty(
$modal_window_js_initialized ) )
    {
// Don't print out these functions twice
       
return;
    }

   
// TODO: asimo> Should not use AdminUI templates for the openModalWindow function. The style part should be handled by css.
   
if( is_admin_page() && isset( $AdminUI ) && $AdminUI->get_template( 'modal_window_js_func' ) !== false )
    {
// Use the modal functions from back-office skin
       
$skin_modal_window_js_func = $AdminUI->get_template( 'modal_window_js_func' );
    }
    elseif( !
is_admin_page() && ! empty( $Blog ) )
    {
// Use the modal functions from front-office skin
       
$blog_skin_ID = $Blog->get_skin_ID();
       
$SkinCache = & get_SkinCache();
       
$Skin = & $SkinCache->get_by_ID( $blog_skin_ID, false, false );
        if(
$Skin && $Skin->get_template( 'modal_window_js_func' ) !== false )
        {
           
$skin_modal_window_js_func = $Skin->get_template( 'modal_window_js_func' );
        }
    }

    if( ! empty(
$skin_modal_window_js_func ) && is_string( $skin_modal_window_js_func ) && function_exists( $skin_modal_window_js_func ) )
    {
// Call skin function only if it exists
       
call_user_func( $skin_modal_window_js_func );
       
$modal_window_js_initialized = true;
        return;
    }

   
$modal_window_js_initialized = true;
}

/**
 * Initialize JavaScript to build and open window for bootstrap skins
 */
function echo_modalwindow_js_bootstrap()
{
   
// Initialize variables for the file "bootstrap-evo_modal_window.js":
   
echo '<script>
        var evo_js_lang_close = \''
.TS_('Close').'\'
        var evo_js_lang_loading = \''
.TS_('Loading...').'\';
        var evo_js_lang_edit_image = \''
.TS_('Insert or edit inline image').'\';
        var evo_js_lang_select_image_insert = \''
.TS_('Select image to insert').'\';
        var evo_js_lang_alert_before_insert_item = \''
.TS_('Save post to start uploading files').'\';
        var evo_js_lang_alert_before_insert_comment = \''
.TS_('Save comment to start uploading files').'\';
        var evo_js_lang_alert_before_insert_emailcampaign = \''
.TS_('Save email campaign to start uploading files').'\';
        var evo_js_lang_alert_before_insert_message = \''
.TS_('Save message to start uploading files').'\';
    </script>'
;
}

/**
 * Initialize JavaScript to build and open WHOIS window
 */
function echo_whois_js_bootstrap()
{
    echo
'<script>
        var evo_js_lang_close = \''
.TS_('Close').'\';
        var evo_js_lang_loading = \''
.TS_('Loading...').'\';
        var evo_js_lang_whois_title = \''
.TS_('Querying WHOIS server...').'\';
    </script>'
;
}


/**
 * Handle fatal error in order to display info message when debug is OFF
 */
function evo_error_handler()
{
    global
$evo_last_handled_error;

   
// Get last error
   
$error = error_get_last();

    if( ! empty(
$error ) && $error['type'] === E_ERROR )
    {
// Save only last fatal error
       
$evo_last_handled_error = $error;
    }

   
// fp> WTF?!? and what about warnings?
    // fp> And where do we die()? why is there not a debug_die() here?
    // There should be ONE MILLION COMMENTS in this function to explain what we do!

}


/**
 * Get icon to collapse/expand fieldset
 *
 * @param string ID of fieldset
 * @param array Params
 * @return string Icon with hidden input field
 */
function get_fieldset_folding_icon( $id, $params = array() )
{
    if( !
is_logged_in() )
    {    
// Only loggedin users can fold fieldset
       
return;
    }

   
$params = array_merge( array(
           
'before'     => '',
           
'after'      => ' ',
           
'deny_fold'  => false, // TRUE to don't allow fold the block and keep it opened always on page loading
           
'default_fold' => NULL, // Set default "fold" value for current icon
           
'fold_value' => NULL,
        ),
$params );

    if(
$params['deny_fold'] )
    {    
// Deny folding for this case
       
$value = 0;
    }
    elseif( !
is_null( $params['fold_value'] ) )
    {    
// Fold value is specified, use this:
       
$value = intval( $params['fold_value'] );
    }
    else
    {    
// Get the fold value from user settings
       
global $UserSettings, $Collection, $Blog, $ctrl;
        if( empty(
$Blog ) || ( isset( $ctrl ) && in_array( $ctrl, array( 'plugins', 'user' ) ) ) )
        {    
// Get user setting value
           
$value = $UserSettings->get( 'fold_'.$id );
        }
        else
        {    
// Get user-collection setting
           
$value = $UserSettings->get_collection_setting( 'fold_'.$id, $Blog->ID );
        }
        if(
$value === NULL && $params['default_fold'] !== NULL )
        {    
// Use custom default value for this icon:
           
$value = $params['default_fold'];
        }
       
$value = intval( $value );
    }

   
// Icon
   
if( $value )
    {
       
$icon_current = 'filters_show';
       
$icon_reverse = 'filters_hide';
       
$title_reverse = T_('Collapse');
    }
    else
    {
       
$icon_current = 'filters_hide';
       
$icon_reverse = 'filters_show';
       
$title_reverse = T_('Expand');
    }
   
$icon = get_icon( $icon_current, 'imgtag', array(
           
'id'         => 'icon_folding_'.$id,
           
'data-xy'    => get_icon( $icon_reverse, 'xy' ),
           
'data-title' => format_to_output( $title_reverse, 'htmlattr' ),
        ) );

   
// Hidden input to store current value of the folding status
   
$hidden_input = '<input type="hidden" name="folding_values['.$id.']" id="folding_value_'.$id.'" value="'.$value.'" />';

    return
$hidden_input.$params['before'].$icon.$params['after'];
}


/**
 * Output JavaScript code to collapse/expand fieldset
 */
function echo_fieldset_folding_js()
{
    if( !
is_logged_in() )
    {
// Only loggedin users can fold fieldset
       
return;
    }

   
expose_var_to_js( 'evo_fieldset_folding_config', true );
}


/**
 * Save the values of fieldset folding into DB
 *
 * @param integer Blog ID is used to save setting per blog, NULL- to don't save per blog
 */
function save_fieldset_folding_values( $blog_ID = NULL )
{
    if( !
is_logged_in() )
    {
// Only loggedin users can fold fieldset
       
return;
    }

   
$folding_values = param( 'folding_values', 'array:integer' );

    if( empty(
$folding_values ) )
    {
// No folding values go from request, Exit here
       
return;
    }

    global
$UserSettings;

    foreach(
$folding_values as $key => $value )
    {
       
$setting_name = 'fold_'.$key;
        if(
$blog_ID !== NULL )
        {
// Save setting per blog
           
$setting_name .= '_'.$blog_ID;
        }
       
$UserSettings->set( $setting_name, $value );
    }

   
// Update the folding setting for current user
   
$UserSettings->dbupdate();
}


/**
 * Save the values of active tab pane into DB
 *
 * @param integer Blog ID is used to save setting per blog, NULL- to don't save per blog
 */
function save_active_tab_pane_value( $blog_ID = NULL )
{
    if( !
is_logged_in() )
    {
// Only loggedin users can fold fieldset
       
return;
    }

   
$tab_pane_value = param( 'tab_pane_active', 'array:string' );

    if( empty(
$tab_pane_value ) )
    {
// No tab pane value go from request, Exit here
       
return;
    }

    global
$UserSettings;

    if(
is_array( $tab_pane_value ) && ! empty( $tab_pane_value ) )
    {    
// Get first key:
       
$key = array_keys( $tab_pane_value );
       
$key = trim( $key[0] );
    }
    else
    {
       
$key = '';
    }

   
$value = ( isset( $tab_pane_value[ $key ] ) ) ? $tab_pane_value[ $key ] : '';
   
$value = trim( $value );
   
    if( ! empty(
$key ) &&  ! empty( $value ) )
    {
       
$setting_name = 'active_'.$key;

        if(
$blog_ID !== NULL )
        {
// Save setting per blog
           
$setting_name .= '_'.$blog_ID;
        }
       
$UserSettings->set( $setting_name, $value );

       
// Update the folding setting for current user
       
$UserSettings->dbupdate();
    }
}


/**
 * Get html code of bootstrap dropdown element
 *
 * @param array Params
 */
function get_status_dropdown_button( $params = array() )
{
   
$params = array_merge( array(
           
'name'             => '',
           
'value'            => '',
           
'title_format'     => '',
           
'options'          => NULL,
           
'exclude_statuses' => array( 'trash' ),
        ),
$params );

    if(
$params['options'] === NULL )
    {    
// Get status options by title format:
       
$status_options = get_visibility_statuses( $params['title_format'], $params['exclude_statuses'] );
    }
    else
    {    
// Use status options from params:
       
$status_options = $params['options'];
    }
   
$status_icon_options = get_visibility_statuses( 'icons', $params['exclude_statuses'] );

   
$r = '<div class="btn-group dropdown autoselected" data-toggle="tooltip" data-placement="top" data-container="body" title="'.get_status_tooltip_title( $params['value'] ).'">';
   
$r .= '<button type="button" class="btn btn-status-'.$params['value'].' dropdown-toggle" data-toggle="dropdown" aria-expanded="false">'
                   
.'<span>'.$status_options[ $params['value'] ].'</span>'
               
.' <span class="caret"></span></button>';
   
$r .= '<ul class="dropdown-menu" role="menu" aria-labelledby="'.$params['name'].'">';
    foreach(
$status_options as $status_key => $status_title )
    {
       
$r .= '<li rel="'.$status_key.'" role="presentation"><a href="#" role="menuitem" tabindex="-1">'.$status_icon_options[ $status_key ].' <span>'.$status_title.'</span></a></li>';
    }
   
$r .= '</ul>';
   
$r .= '</div>';

    return
$r;
}

/**
 * Output JavaScript code to work with dropdown bootstrap element
 */
function echo_form_dropdown_js()
{
   
// Build a string to initialize javascript array with button titles
   
$tooltip_titles = get_visibility_statuses( 'tooltip-titles' );
   
$tooltip_titles_js_array = array();
    foreach(
$tooltip_titles as $status => $tooltip_title )
    {
       
$tooltip_titles_js_array[] = $status.': \''.TS_( $tooltip_title ).'\'';
    }
   
$tooltip_titles_js_array = implode( ', ', $tooltip_titles_js_array );
?>
<script>
jQuery( '.btn-group.dropdown.autoselected li a' ).on( 'click', function()
{
    var item_status_tooltips = {<?php echo $tooltip_titles_js_array ?>};
    var item = jQuery( this ).parent();
    var status = item.attr( 'rel' );
    var btn_group = item.parent().parent();
    var button = jQuery( item.parent().parent().find( 'button:first' ) );
    var field_name = jQuery( this ).parent().parent().attr( 'aria-labelledby' );

    // Change status class name to new changed for all buttons:
    button.attr( 'class', button.attr( 'class' ).replace( /btn-status-[^\s]+/, 'btn-status-' + status ) );
    // Update selector button to status title:
    button.find( 'span:first' ).html( item.find( 'span:last' ).html() ); // update selector button to status title
    // Update hidden field to new status value:
    jQuery( 'input[type=hidden][name=' + field_name + ']' ).val( status );
    // Hide dropdown menu:
    item.parent().parent().removeClass( 'open' );

    // Update tooltip
    btn_group.tooltip( 'hide' ).attr( 'data-original-title', item_status_tooltips[status] ).tooltip( 'show' );

    return false;
} );
</script>
<?php
}


/**
 * Get baseurl depending on current called script
 *
 * @return string URL
 */
function get_script_baseurl()
{
    if( isset(
$_SERVER['SERVER_NAME'] ) )
    {
// Set baseurl from current server name

       
$temp_baseurl = 'http://'.$_SERVER['SERVER_NAME'];

        if( isset(
$_SERVER['SERVER_PORT'] ) )
        {
            if(
$_SERVER['SERVER_PORT'] == '443' )
            {    
// Rewrite that as https:
               
$temp_baseurl = 'https://'.$_SERVER['SERVER_NAME'];
            }
            elseif(
$_SERVER['SERVER_PORT'] == '8890' )
            {    
// Used for testing
               
$temp_baseurl = 'https://'.$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'];
            }
            elseif(
$_SERVER['SERVER_PORT'] != '80' )
            {
// Get also a port number
               
$temp_baseurl .= ':'.$_SERVER['SERVER_PORT'];
            }
        }

        if( isset(
$_SERVER['SCRIPT_NAME'] ) )
        {
// Get also the subfolders, when script is called e.g. from http://localhost/blogs/b2evolution/
           
$temp_baseurl .= preg_replace( '~(.*/)[^/]*$~', '$1', $_SERVER['SCRIPT_NAME'] );
        }
    }
    else
    {
// Use baseurl from config
       
global $baseurl;
       
$temp_baseurl = $baseurl;
    }

    return
$temp_baseurl;
}


/**
 * Get badge to inform the settings are edited only by collection/user admins
 *
 * @param string Type: 'coll', 'user'
 * @param string Manual URL, '#' - default, false - don't set URL
 * @param string Text
 * @param string Title
 * @param string Value
 * @return string
 */
function get_admin_badge( $type = 'coll', $manual_url = '#', $text = '#', $title = '#', $value = NULL )
{
    switch(
$type )
    {
        case
'coll':
            if(
$text == '#' )
            {    
// Use default text:
               
$text = T_('Coll. Admin');
            }
            if(
$title == '#' )
            {    
// Use default title:
               
$title = T_('This can only be edited by users with the Collection Admin permission.');
            }
            if(
$manual_url == '#' )
            {    
// Use default manual url:
               
$manual_url = 'collection-admin';
            }
            break;

        case
'user':
            if(
$text == '#' )
            {    
// Use default text:
               
$text = T_('User Admin');
            }
            if(
$title == '#' )
            {    
// Use default title:
               
$title = T_('This can only be edited by users with the User Admin permission.');
            }
            if(
$manual_url == '#' )
            {    
// Use default manual url:
               
$manual_url = 'user-admin';
            }
            break;

        default:
           
// Unknown badge type:
           
return '';
    }

    if( empty(
$manual_url ) )
    {    
// Don't use a link:
       
$r = ' <b';
    }
    else
    {    
// Use link:
       
$r = ' <a href="'.get_manual_url( $manual_url ).'" target="_blank"';
    }
   
$r .= ' class="badge badge-warning"';
    if( ! empty(
$title ) && $title != '#' )
    {    
// Use title for tooltip:
       
$r .= ' data-toggle="tooltip" data-placement="top" title="'.format_to_output( $title, 'htmlattr' ).'"';
    }
   
$r .= '>';
   
$r .= $text;
    if( empty(
$manual_url ) )
    {    
// End of text formatted badge:
       
$r .= '</b>';
    }
    else
    {    
// End of the link:
       
$r .= '</a>';
    }

    return
$r;
}


/**
 * Compares two "PHP-standardized" version number strings
 *
 * @param string First version number, Use 'current' for global $app_version
 * @param string Second version number
 * @param string If the third optional operator argument is specified, test for a particular relationship.
 *               The possible operators are: <, lt, <=, le, >, gt, >=, ge, ==, =, eq, !=, <>, ne respectively.
 *               This parameter is case-sensitive, values should be lowercase.
 * @return integer|boolean -1 if the first version is lower than the second, 0 if they are equal, and 1 if the second is lower.
 *                         When using the optional operator argument, the function will return TRUE if the relationship is the one specified by the operator, FALSE otherwise.
 */
function evo_version_compare( $version1, $version2, $operator = NULL )
{
    if(
$version1 === 'current' )
    {    
// Use current version of application:
       
global $app_version;
       
$version1 = $app_version;
    }

   
preg_match( '#^([\d\.]+)(-.+)?$#', $version1, $m_ver1 );
   
preg_match( '#^([\d\.]+)(-.+)?$#', $version2, $m_ver2 );

    if( isset(
$m_ver1[1], $m_ver2[1] ) && $m_ver1[1] == $m_ver2[1] )
    {    
// If versions number is same:
       
$version1_suffix = ( isset( $m_ver1[2] ) ? $m_ver1[2] : '' );
       
$version2_suffix = ( isset( $m_ver2[2] ) ? $m_ver2[2] : '' );

        if(
$version1_suffix == '-PRO' )
        {    
// Remove "PRO" suffix to compare such versions as upper than "stable", "alpha", "beta" and etc.:
           
$version1 = $m_ver1[1];
            if(
$version2_suffix === '' )
            {    
// Add suffix "stable" in order to make version(without suffix) lower than "PRO":
               
$version2 .= '-stable';
            }
        }
        elseif(
$version2_suffix == '-stable' )
        {    
// Remove "stable" suffix to compare such versions as upper than "alpha", "beta" and etc. except of "PRO":
           
$version2 = $m_ver2[1];
        }

        if(
$version2_suffix == '-PRO' )
        {    
// Remove "PRO" suffix to compare such versions as upper than "stable", "alpha", "beta" and etc.:
           
$version2 = $m_ver2[1];
            if(
$version1_suffix === '' )
            {    
// Add suffix "stable" in order to make version(without suffix) lower than "PRO":
               
$version1 .= '-stable';
            }
        }
        elseif(
$version1_suffix == '-stable' )
        {    
// Remove "stable" suffix to compare such versions as upper than "alpha", "beta" and etc. except of "PRO":
           
$version1 = $m_ver1[1];
        }
    }

    if(
is_null( $operator ) )
    {    
// To return integer:
       
return version_compare( $version1, $version2 );
    }
    else
    {    
// To return boolean:
       
return version_compare( $version1, $version2, $operator );
    }
}


/**
 * Get text for install page depending on param $display == 'cli'
 *
 * @param string Original text
 * @param string Format (Used for CLI mode)
 * @return string Prepared text
 */
function get_install_format_text_and_log( $text, $format = 'string' )
{
    global
$display, $logs_path, $log_file_handle, $avoid_log_file;

    if( empty(
$display ) || $display != 'cli' )
    {    
// Don't touch text for non CLI modes:
       
if( ! $avoid_log_file )
        {    
// Include in log file:
           
prepare_install_log_message( $text );
        }
        return
$text;
    }

   
// Don't remove these HTML tags on CLI mode:
   
$allowable_html_tags = '<evo:error><evo:warning><evo:success><evo:note><evo:login><evo:password>';

   
// Remove all new lines because we build them from requested format:
   
$text = str_replace( array( "\n", "\r" ), '', $text );

   
// Keep all URLs and display them
   
$text = preg_replace( '/<a[^>]+href="([^"]+)"[^>]*>(.+)<\/a>/i', '$2(URL: $1)', $text );

   
// Remove HTML tags from text:
   
$text = strip_tags( $text, $allowable_html_tags );

    switch(
$format )
    {
        case
'h2':
           
// Header 2
           
$text = "\n\n----- ".$text." -----\n\n";
            break;

        case
'br':
           
// Paragraph:
           
$text = $text."\n";
            break;

        case
'p':
           
// Paragraph:
           
$text = "\n".$text."\n\n";
            break;

        case
'p-start':
           
// Start paragraph:
           
$text = "\n".$text;
            break;

        case
'p-start-br':
           
// Start paragraph:
           
$text = "\n".$text."\n";
            break;

        case
'p-end':
           
// End paragraph:
           
$text = $text."\n\n";
            break;

        case
'li':
           
// List item:
           
$text = "\n- ".$text."\n";
            break;

        case
'code':
           
// Code:
           
$text = "\n================\n".$text."\n================\n";
            break;
    }

   
// Replace all html entities like "&nbsp;", "&raquo;", "&laquo;" to readable chars:
   
$text = html_entity_decode( $text );
   
    if( !
$avoid_log_file )
    {    
// Include in log file:
       
prepare_install_log_message( $text );
    }
   
    return
$text;
}


/**
* Start to log into file on disk
*/
function start_install_log( $log_file_name )
{    
// TODO: Factorize with start_log():
   
global $rsc_url, $app_version_long, $log_file_handle, $logs_path, $servertimenow;

   
// Get file path for log:
   
$log_file_path = $logs_path.date( 'Y-m-d-H-i-s', $servertimenow ).'-'.$log_file_name.'.html';
   
   
// Check log path is writeable or not:
   
if ( ! is_writable( $logs_path ) ) {

        return
false;
    }
   
   
// Try to create log file:
   
if( ! ( $log_file_handle = fopen( $log_file_path, 'w' ) ) )
    {    
        return
false;
    }

   
// Write header of the log file:
   
install_log_to_file( '<!DOCTYPE html>'."\r\n"
       
.'<html lang="en-US">'."\r\n"
       
.'<head>'."\r\n"
       
.'<link href="'.$rsc_url.'css/bootstrap/bootstrap.css?v='.$app_version_long.'" type="text/css" rel="stylesheet" />'."\r\n"
       
.'</head>'."\r\n"
       
.'<body>'."\r\n"
       
.'<div style="padding:5px">' );
}


/**
* End of log into file on disk
*/
function end_install_log()
{    
// TODO: Factorize with end_log():
   
global $log_file_handle;

   
// Write footer of the log file:
   
install_log_to_file( '</div>'."\r\n"
       
.'</body>'."\r\n"
       
.'</html>' );

    if( isset(
$log_file_handle ) && $log_file_handle )
    {    
// Close the log file:
       
fclose( $log_file_handle );
    }
}


/**
* Log a message on screen and into file on disk
*
* @param string Message
* @param string Type: 'success', 'error', 'warning'
* @param string HTML tag for type/styled log: 'p', 'span', 'b', etc.
* @param boolean TRUE to display label
*/
function prepare_install_log_message( $message, $type = NULL, $type_html_tag = 'p', $display_label = true )
{    
// TODO: Factorize with log():
   
global $log_file_handle;

    if( ! isset(
$log_file_handle ) || ! $log_file_handle )
    {    
        return
false;
    }
   
   
$message = get_install_log( $message, $type, $type_html_tag, $display_label );

    if(
$message === false )
    {    
// Skip when message should not be displayed:
       
return;
    }

   
// Try to store a message into the log file on the disk:
   
install_log_to_file( $message );
}


/**
* Get a log message
*
* @param string Message
* @param string Type: 'success', 'error', 'warning', 'info'
* @param string HTML tag for type/styled log: 'p', 'span', 'b', etc.
* @param boolean TRUE to display label
* @return string|FALSE Formatted log message, FALSE - when message should not be displayed
*/
function get_install_log( $message, $type = NULL, $type_html_tag = 'p', $display_label = true )
{    
// TODO: Factorize with get_log():
   
if( $message === '' )
    {    
// Don't log empty strings:
       
return false;
    }

    switch(
$type )
    {
        case
'success':
           
$before = '<'.$type_html_tag.' class="text-success"> ';
           
$after = '</'.$type_html_tag.'>';
            break;

        case
'error':
           
$before = '<'.$type_html_tag.' class="text-danger">'.( $display_label ? '<span class="label label-danger">ERROR</span>' : '' ).' ';
           
$after = '</'.$type_html_tag.'>';
            break;

        case
'warning':
           
$before = '<'.$type_html_tag.' class="text-warning">'.( $display_label ? '<span class="label label-warning">WARNING</span>' : '' ).' ';
           
$after = '</'.$type_html_tag.'>';
            break;

        case
'info':
           
$before = '<'.$type_html_tag.' class="text-info">'.( $display_label ? '<span class="label label-info">INFO</span>' : '' ).' ';
           
$after = '</'.$type_html_tag.'>';
            break;

        default:
           
$before = '';
           
$after = '';
            break;
    }

    return
$before.$message.$after;
}


/**
* Log a message into file on disk
*
* @param string Message
*/
function install_log_to_file( $message )
{    
// TODO: Factorize with log_to_file():
   
global $log_file_handle;

    if( ! isset(
$log_file_handle ) || ! $log_file_handle )
    {    
        return
false;
    }

   
// Put a message into the log file on the disk:
   
fwrite( $log_file_handle, $message."\r\n" );
}


/**
 * Check if password should be transmitted in hashed format during Login
 *
 * @return boolean TRUE - hashed password will be transmitted, FALSE - raw password will be transmitted
 */
function can_use_hashed_password()
{
    global
$transmit_hashed_password;

    if( isset(
$transmit_hashed_password ) )
    {    
// Get value from already defined var:
       
return $transmit_hashed_password;
    }

    global
$Settings, $Plugins;

   
// Allow to transmit hashed password only when:
    // - it is enabled by general setting "Password hashing during Login"
    // - no plugins that automatically disable this option during Login
   
$transmit_hashed_password = (bool)$Settings->get( 'js_passwd_hashing' ) && !(bool)$Plugins->trigger_event_first_true( 'LoginAttemptNeedsRawPassword' );

    return
$transmit_hashed_password;
}


/**
 * Convert inline file tags like [image|file:123:link title:.css_class_name] or [inline:123:.css_class_name] into HTML tags
 *
 * @param string Source content
 * @param object Source object: Item, Comment, EmailCampaign, Message
 * @param array Params
 * @return string Content
 */
function render_inline_files( $content, $Object, $params = array() )
{
   
$params = array_merge( array(
           
'check_code_block' => false,
           
'clear_paragraph'  => true,
           
'render_tag_image'     => true,
           
'render_tag_file'      => true,
           
'render_tag_inline'    => true,
           
'render_tag_video'     => true,
           
'render_tag_audio'     => true,
           
'render_tag_thumbnail' => true,
           
'render_tag_folder'    => true,
        ),
$params );

       
$render_tags = array();
        if(
$params['render_tag_image'] )
        {    
// Render short tag [image:]
           
$render_tags[] = 'image';
        }
        if(
$params['render_tag_file'] )
        {    
// Render short tag [file:]
           
$render_tags[] = 'file';
        }
        if(
$params['render_tag_inline'] )
        {    
// Render short tag [inline:]
           
$render_tags[] = 'inline';
        }
        if(
$params['render_tag_video'] )
        {    
// Render short tag [video:]
           
$render_tags[] = 'video';
        }
        if(
$params['render_tag_audio'] )
        {    
// Render short tag [audio:]
           
$render_tags[] = 'audio';
        }
        if(
$params['render_tag_thumbnail'] )
        {    
// Render short tag [thumbnail:]
           
$render_tags[] = 'thumbnail';
        }
        if(
$params['render_tag_folder'] )
        {    
// Render short tag [folder:]
           
$render_tags[] = 'folder';
        }

        if( empty(
$render_tags ) )
        {    
// No tags for rendering:
           
return $content;
        }

    if(
$params['check_code_block'] && ( ( stristr( $content, '<code' ) !== false ) || ( stristr( $content, '<pre' ) !== false ) ) )
    {    
// Call render_inline_files() on everything outside code/pre:
       
$params['check_code_block'] = false;
       
$content = callback_on_non_matching_blocks( $content,
           
'~<(code|pre)[^>]*>.*?</\1>~is',
           
'render_inline_files', array( $Object, $params ) );
        return
$content;
    }

   
// No code/pre blocks, replace on the whole thing

   
if( $params['clear_paragraph'] )
    {    
// Remove block level short tags inside <p> blocks and move them before the paragraph:
       
$content = move_short_tags( $content );
    }

   
// Find all matches with inline tags
   
preg_match_all( '/\[('.implode( '|', $render_tags ).'):(\d+)(:?)([^\]]*)\]/i', $content, $inlines );

    if( !empty(
$inlines[0] ) )
    {    
// There are inline tags in the content...

       
$rendered_tags = render_inline_tags( $Object, $inlines[0], $params );
        if(
$rendered_tags )
        {    
// Do replacing if the object really contains inline attached tags:
           
foreach( $rendered_tags as $current_link_tag => $rendered_link_tag )
            {
               
$content = str_replace( $current_link_tag, $rendered_link_tag, $content );
            }
        }
    }

    return
$content;
}


/**
 * Convert inline tags like [image:|file:|inline:|video:|audio:|thumbnail:|folder:] into HTML tags
 *
 * @param object Source object: Item, Comment, EmailCampaign, Message
 * @param array Inline tags
 * @param array Params
 * @return array Associative array of rendered HTML tags with inline tags as key
 */
function render_inline_tags( $Object, $tags, $params = array() )
{
    global
$Plugins;
   
$inlines = array();

   
$object_class = get_class( $Object );

   
$params = array_merge( array(
               
'before'                   => '<div>',
               
'before_image'             => '<div'.( $object_class == 'EmailCampaign' ? emailskin_style( '.image_block' ) : ' class="image_block"' ).'>',
               
'before_image_legend'      => '<div'.( $object_class == 'EmailCampaign' ? emailskin_style( '.image_legend' ) : ' class="image_legend"' ).'>',
               
'after_image_legend'       => '</div>',
               
'after_image'              => '</div>',
               
'after'                    => '</div>',
               
'image_size'               => 'fit-400x320',
               
'image_link_to'            => 'original', // Can be 'orginal' (image) or 'single' (this post)
               
'limit'                    => 1000, // Max # of images displayed
           
), $params );

    if( !isset(
$LinkList ) )
    {    
// Get list of attached Links only first time:
       
if( $Object->ID == 0 )
        {    
// Get temporary object ID on preview new creating object:
           
$temp_link_owner_ID = param( 'temp_link_owner_ID', 'integer', NULL );
        }
        else
        {    
// Don't use temporary object for existing object:
           
$temp_link_owner_ID = NULL;
        }

        switch(
$object_class )
        {
            case
'Item':
               
$LinkOwner = new LinkItem( $Object, $temp_link_owner_ID );
               
$prepare_plugin_event_name = 'PrepareForRenderItemAttachment';
               
$render_plugin_event_name = 'RenderItemAttachment';
                break;

            case
'Comment':
               
$LinkOwner = new LinkComment( $Object, empty( $Object->temp_link_owner_ID ) ? $temp_link_owner_ID : $Object->temp_link_owner_ID );
               
$prepare_plugin_event_name = 'PrepareForRenderCommentAttachment';
               
$render_plugin_event_name = 'RenderCommentAttachment';
                break;

            case
'EmailCampaign':
               
$LinkOwner = new LinkEmailCampaign( $Object );
               
$prepare_plugin_event_name = 'PrepareForRenderEmailAttachment';
               
$render_plugin_event_name = 'RenderEmailAttachment';
                break;

            case
'Message':
               
$LinkOwner = new LinkMessage( $Object, $temp_link_owner_ID );
               
$prepare_plugin_event_name = 'PrepareForRenderMessageAttachment';
               
$render_plugin_event_name = 'RenderMessageAttachment';
                break;

            default:
               
// Wrong source object type:
               
return false;
        }
       
$LinkList = $LinkOwner->get_attachment_LinkList( $params['limit'] );
    }

    if( empty(
$LinkList ) )
    {    
// This Object has no attached files for 'inline' position, Exit here:
       
return false;
    }

   
// Render inline tags by active plugins:
   
$plugins_inlines = $Plugins->trigger_collect( 'RenderInlineTags', array_merge( $params, array(
           
'Object'      => $Object,
           
'inline_tags' => $tags,
        ) ) );
    foreach(
$plugins_inlines as $plugin_ID => $plugin_inlines )
    {
       
$inlines = array_merge( $inlines, $plugin_inlines );
    }

    foreach(
$tags as $current_inline )
    {
        if( isset(
$inlines[$current_inline] ) )
        {    
// Skip inline tag if it has been already rendered before, e-g by some plugin in the event "RenderInlineTags" above:
           
continue;
        }

        if( !
preg_match( '/\[(image|file|inline|video|audio|thumbnail|folder):(\d+)(:?)([^\]]*)\]/i', $current_inline, $inline ) )
        {    
// Don't render a not supported inline tag:
           
$inlines[$current_inline] = $current_inline;
            continue;
        }

       
$inline_type = $inline[1]; // image|file|inline|video|audio|thumbnail|folder
       
$current_link_ID = (int) $inline[2];

        if( empty(
$current_link_ID ) )
        {    
// Invalid link ID, Go to next match
           
$inlines[$current_inline] = $current_inline;
            continue;
        }

        if( ! (
$Link = & $LinkList->get_by_field( 'link_ID', $current_link_ID ) ) )
        {    
// Link ID is not part of the linked files for position "inline"
           
$inlines[$current_inline] = $current_inline;
            continue;
        }

        if( ! (
$File = & $Link->get_File() ) )
        {    
// No File object:
           
global $Debuglog;
           
$Debuglog->add( sprintf( 'Link ID#%d of '.$object_class.' #%d does not have a file object!', $Link->ID, $Object->ID ), array( 'error', 'files' ) );
           
$inlines[$current_inline] = $current_inline;
            continue;
        }

        if( !
$File->exists() )
        {    
// File doesn't exist:
           
global $Debuglog;
           
$Debuglog->add( sprintf( 'File linked to '.$object_class.' #%d does not exist (%s)!', $Object->ID, $File->get_full_path() ), array( 'error', 'files' ) );
           
$inlines[$current_inline] = $current_inline;
            continue;
        }

       
$params['File'] = $File;
       
$params['Link'] = $Link;
       
$params[ $object_class ] = $Object;

       
$current_file_params = array();

        switch(
$inline_type )
        {
            case
'image':
            case
'inline': // valid file type: image
               
if( $File->is_image() )
                {
                   
$current_image_params = $params;
                   
$image_href = false;
                   
$image_rel = NULL;
                   
$image_additional_class = false;

                    if( ! empty(
$inline[3] ) ) // check if second colon is present
                   
{
                       
// Get the inline params: caption and class
                       
$inline_params = explode( ':', $inline[4] );
                       
$opt_index = 0;

                       
// Caption:
                       
if( $inline_type != 'inline' && ! empty( $inline_params[0] ) )
                        {    
// Caption is set, so overwrite the image link title
                           
if( $inline_params[0] == '-' )
                            {    
// Caption display is disabled
                               
$current_image_params['image_link_title'] = '';
                               
$current_image_params['hide_image_link_title'] = true;
                            }
                            else
                            {    
// New image caption was set
                               
$current_image_params['image_link_title'] = strip_tags( $inline_params[0] );
                            }

                           
$current_image_params['image_desc'] = $current_image_params['image_link_title'];
                           
$current_file_params['title'] = $inline_params[0];
                        }
                        if(
$inline_type == 'image' )
                        {    
// Caption has always a reserved placefor image short tag:
                           
$opt_index++;
                        }

                       
// RegExp to detect HRef option:
                       
$href_regexp = '#^(https?|\(\((.*?)\)\))$#i';

                       
// Alt text:
                       
$current_image_params['image_alt'] = '';
                        if( isset(
$inline_params[ $opt_index ] ) &&
                           
substr( $inline_params[ $opt_index ], 0, 1 ) != '.' &&
                            !
preg_match( $href_regexp, $inline_params[ $opt_index ] ) &&
                            (
$inline_type == 'image' || ! in_array( $inline_params[ $opt_index ], array( 'small', 'medium', 'large', 'original' ) ) ) )
                        {    
// Override the image File's alt text with provided in current inline tag:
                           
if( $inline_params[ $opt_index ] == '-' )
                            {    
// Alt text display is disabled:
                               
$current_image_params['image_alt'] = '-';
                            }
                            else
                            {    
// New image alt text was set:
                               
$current_image_params['image_alt'] = strip_tags( $inline_params[ $opt_index ] );
                            }
                           
$opt_index++;
                        }

                       
// HRef:
                       
if( $inline_type != 'inline' &&
                            ! empty(
$inline_params[ $opt_index ] ) &&
                           
preg_match( $href_regexp, $inline_params[ $opt_index ], $href_match ) )
                        {
                            if(
stripos( $href_match[0], 'http' ) === 0 )
                            {    
// Absolute URL:
                               
$image_href = $href_match[0].':'.$inline_params[ $opt_index + 1 ];
                               
$image_rel = ''; // reset default attribute "rel" to don't display colorbox on click
                               
$opt_index++;
                            }
                            else
                            {    
// Item slug:
                               
$ItemCache = & get_ItemCache();
                                if(
$href_match[2] === '' )
                                {    
// No link, Display image tag without link tag around:
                                   
$image_href = '';
                                }
                                elseif(
$slug_Item = & $ItemCache->get_by_urltitle( $href_match[2], false, false ) )
                                {    
// Use a link with item permanent url around image tag:
                                   
$image_href = $slug_Item->get_permanent_url();
                                }
                                else
                                {    
// Wrong Item provided, Singal with special red class:
                                   
$image_href = '';
                                   
$image_additional_class = 'imgerror';
                                }
                               
$image_rel = ''; // reset default attribute "rel" to don't display colorbox on click
                           
}
                           
$opt_index++;
                        }

                       
$current_image_params['image_link_to'] = ( $image_href === false ? 'original' : $image_href );
                       
$current_image_params['image_link_rel'] = $image_rel;

                       
// TODO: Size:

                        // Class Name(s):
                       
$inline_param_class = ( empty( $inline_params[ $opt_index ] ) ? '' : $inline_params[ $opt_index ] );
                        if(
$image_additional_class !== false )
                        {    
// Append additional class, e.g. on wrong provided item slug:
                           
$inline_param_class .= '.'.$image_additional_class;
                        }
                        if( ! empty(
$inline_param_class ) )
                        {    
// A class name is set for the inline tags
                           
$image_extraclass = strip_tags( trim( str_replace( '.', ' ', $inline_param_class ) ) );

                            if(
preg_match('#^[A-Za-z0-9\s\-_]+$#', $image_extraclass ) )
                            {
                                if(
$object_class == 'EmailCampaign' )
                                {    
// Append extra class to image/file inline img tags:
                                   
$current_image_params['image_class'] = $image_extraclass;
                                }
                                else
                                {    
// Inject extra class name(s) into 'before_image' param:
                                   
$current_image_params['before_image'] = update_html_tag_attribs( $current_image_params['before_image'], array( 'class' => $image_extraclass ) );
                                }
                               
$current_file_params['class'] = $image_extraclass;
                            }
                        }
                    }

                    foreach(
$current_image_params as $param_key => $param_value )
                    {    
// Pass all params by reference, in order to give possibility to modify them by plugin
                        // So plugins can add some data before/after image tags (E.g. used by infodots plugin)
                       
$current_image_params[ $param_key ] = & $current_image_params[ $param_key ];
                    }

                   
// Prepare params before rendering attachment:
                   
$Plugins->trigger_event_first_true_with_params( $prepare_plugin_event_name, $current_image_params );

                   
// Render attachments by plugin, Append the html content to $current_image_params['data'] and to $r:
                   
if( count( $Plugins->trigger_event_first_true_with_params( $render_plugin_event_name, $current_image_params ) ) != 0 )
                    {    
// This attachment has been rendered by a plugin (to $current_image_params['data']):
                       
$inlines[ $current_inline ] = $current_image_params['data'];
                        break;
                    }

                    if(
$inline_type == 'image' )
                    {    
// Generate the IMG tag with all the alt, title and desc if available:
                       
switch( $object_class )
                        {
                            case
'Item':
                               
// Get the IMG tag with link to original image or to Item page:
                               
$inlines[ $current_inline ] = $Object->get_attached_image_tag( $Link, $current_image_params );
                                break;

                            case
'EmailCampaign':
                               
// Get the IMG tag without link for email content:
                               
$image_style = '';
                                if( ! empty(
$current_image_params['image_class'] ) )
                                {    
// Convert classes to style format:
                                   
$image_style = emailskin_style( '.'.str_replace( ' ', '+.', $current_image_params['image_class'] ), false );
                                   
// We cannot use class attribute on email campaign content:
                                   
unset( $current_image_params['image_class'] );
                                }

                               
$inlines[ $current_inline ] = $Link->get_tag( array_merge( $current_image_params, array(
                                       
'image_link_to' => $image_href,
                                       
'image_style'   => 'border: none; max-width: 100%; height: auto;'.$image_style,
                                       
'add_loadimg'   => false,
                                    ) ) );
                                break;

                            default:
                               
// Get the IMG tag with link to original big image:
                               
$inlines[ $current_inline ] = $Link->get_tag( array_merge( $params, $current_image_params ) );
                                break;
                        }
                    }
                    elseif(
$inline_type == 'inline' )
                    {    
// Generate simple IMG tag with resized image size:
                       
switch( $object_class )
                        {
                            case
'EmailCampaign':
                               
$image_style = '';
                                if( ! empty(
$current_file_params['class'] ) )
                                {    
// Convert classes to style format:
                                   
$image_style = emailskin_style( '.'.str_replace( ' ', '+.', $current_file_params['class'] ), false );
                                   
// We cannot use class attribute on email campaign content:
                                   
unset( $current_file_params['class'] );
                                }
                               
$inlines[ $current_inline ] = $File->get_tag( '', '', '', '', $current_image_params['image_size'], '', '', '',
                                       
'', '', $current_image_params['image_alt'], '', '', 1, NULL, 'border: none; max-width: 100%; height: auto;'.$image_style, false );
                                break;

                            default:
                               
$inlines[ $current_inline ] = $File->get_tag( '', '', '', '', $current_image_params['image_size'], '', '', '',
                                        ( empty(
$current_file_params['class'] ) ? '' : $current_file_params['class'] ), '', $current_image_params['image_alt'], '' );
                        }
                    }
                }
                else
                {
// not an image file, do not process
                   
$inlines[$current_inline] = $current_inline;
                }
                break;

            case
'thumbnail':
                if(
$File->is_image() )
                {
                    global
$thumbnail_sizes;

                   
$thumbnail_alt = '';
                   
$thumbnail_href = false;
                   
$thumbnail_rel = NULL;
                   
$thumbnail_additional_class = false;
                   
$thumbnail_size = 'medium';
                   
$thumbnail_position = 'left';

                   
$thumbnail_classes = array();

                    if( ! empty(
$inline[3] ) ) // check if second colon is present
                   
{
                       
// Get optional inline params: HRef, Size, Alignment, Class
                       
$inline_params = explode( ':', $inline[4] );
                       
$opt_index = 0;

                       
// RegExp to detect HRef option:
                       
$href_regexp = '#^(https?|\(\((.*?)\)\))$#i';

                       
// Alt text:
                       
if( isset( $inline_params[ $opt_index ] ) &&
                           
substr( $inline_params[ $opt_index ], 0, 1 ) != '.' &&
                            !
preg_match( $href_regexp, $inline_params[ $opt_index ] ) &&
                            !
in_array( $inline_params[ $opt_index ], array( 'small', 'medium', 'large', 'left', 'right' ) ) )
                        {    
// Override the image File's alt text with provided in current inline tag:
                           
if( $inline_params[ $opt_index ] == '-' )
                            {    
// Alt text display is disabled:
                               
$thumbnail_alt = '-';
                            }
                            else
                            {    
// New image alt text was set:
                               
$thumbnail_alt = strip_tags( $inline_params[ $opt_index ] );
                            }
                           
$opt_index++;
                        }

                       
// HRef:
                       
if( ! empty( $inline_params[ $opt_index ] ) &&
                           
preg_match( $href_regexp, $inline_params[ $opt_index ], $href_match ) )
                        {
                            if(
stripos( $href_match[0], 'http' ) === 0 )
                            {    
// Absolute URL:
                               
$thumbnail_href = $href_match[0].':'.$inline_params[ $opt_index + 1 ];
                               
$thumbnail_rel = ''; // reset default attribute "rel" to don't display colorbox on click
                               
$opt_index++;
                            }
                            else
                            {    
// Item slug:
                               
$ItemCache = & get_ItemCache();
                                if(
$href_match[2] === '' )
                                {    
// No link, Display image tag without link tag around:
                                   
$thumbnail_href = '';
                                }
                                elseif(
$slug_Item = & $ItemCache->get_by_urltitle( $href_match[2], false, false ) )
                                {    
// Use a link with item permanent url around image tag:
                                   
$thumbnail_href = $slug_Item->get_permanent_url();
                                }
                                else
                                {    
// Wrong Item provided, Singal with special red class:
                                   
$thumbnail_href = '';
                                   
$thumbnail_additional_class = 'imgerror';
                                }
                               
$thumbnail_rel = ''; // reset default attribute "rel" to don't display colorbox on click
                           
}
                           
$opt_index++;
                        }

                       
// Size:
                       
$valid_thumbnail_sizes = array( 'small', 'medium', 'large' );
                        if( ! empty(
$inline_params[ $opt_index ] ) && in_array( $inline_params[ $opt_index ], $valid_thumbnail_sizes ) )
                        {
                           
$thumbnail_size = $inline_params[ $opt_index ];
                           
$opt_index++;
                        }

                       
// Alignment:
                       
$valid_thumbnail_positions = array( 'left', 'right' );
                        if( ! empty(
$inline_params[ $opt_index ] ) && in_array( $inline_params[ $opt_index ], $valid_thumbnail_positions ) )
                        {
                           
$thumbnail_position = $inline_params[ $opt_index ];
                           
$opt_index++;
                        }

                       
// Class:
                       
$inline_param_class = ( empty( $inline_params[ $opt_index ] ) ? '' : $inline_params[ $opt_index ] );
                        if(
$thumbnail_additional_class !== false )
                        {    
// Append additional class, e.g. on wrong provided item slug:
                           
$inline_param_class .= '.'.$thumbnail_additional_class;
                        }
                        if( ! empty(
$inline_param_class ) )
                        {    
// A class name is set for the inline tags
                           
$extra_classes = explode( '.', ltrim( $inline_param_class, '.' ) );
                        }
                    }

                    switch(
$thumbnail_size )
                    {
                        case
'small':
                           
$thumbnail_size = 'fit-128x128';
                            break;

                        case
'large':
                           
$thumbnail_size = 'fit-320x320';
                            break;

                        case
'medium':
                        default:
                           
$thumbnail_size = 'fit-192x192';
                            break;
                    }

                   
$thumbnail_classes[] = 'evo_thumbnail';
                   
$thumbnail_classes[] = 'evo_thumbnail__'.$thumbnail_position;
                    if( isset(
$extra_classes ) )
                    {
                       
$thumbnail_classes = array_merge( $thumbnail_classes, $extra_classes );
                    }

                   
$current_image_params = array(
                       
'before_image'        => '',
                       
'before_image_legend' => '', // can be NULL
                       
'after_image_legend'  => '',
                       
'after_image'         => '',
                       
'image_size'          => $thumbnail_size,
                       
'image_link_to'       => ( $thumbnail_href === false ? 'original' : $thumbnail_href ),
                       
'image_link_title'    => '',    // can be text or #title# or #desc#
                       
'image_link_rel'      => $thumbnail_rel,
                       
'image_class'         => implode( ' ', $thumbnail_classes ),
                       
'image_alt'           => $thumbnail_alt,
                    );

                    switch(
$object_class )
                    {
                        case
'Item':
                           
// Get the IMG tag with link to original image or to Item page:
                           
$inlines[ $current_inline ] = $Object->get_attached_image_tag( $Link, $current_image_params );
                            break;

                        case
'EmailCampaign':
                           
// Get the IMG tag without link for email content:
                           
$image_style = '';
                            if( ! empty(
$current_image_params['image_class'] ) )
                            {    
// Convert classes to style format:
                               
$image_style = emailskin_style( '.'.str_replace( ' ', '+.', $current_image_params['image_class'] ), false );
                               
// We cannot use class attribute on email campaign content:
                               
unset( $current_image_params['image_class'] );
                            }
                           
$inlines[ $current_inline ] = $Link->get_tag( array_merge( $current_image_params, array(
                                   
'image_link_to' => $thumbnail_href,
                                   
'image_style'   => 'border: none; max-width: 100%; height: auto;'.$image_style,
                                   
'add_loadimg'   => false,
                                ) ) );
                            break;

                        default:
                           
// Get the IMG tag with link to original big image:
                           
$inlines[ $current_inline ] = $Link->get_tag( array_merge( $params, $current_image_params ) );
                            break;
                    }
                }
                else
                {    
// not an image file, do not process
                   
$inlines[$current_inline] = $current_inline;
                }
                break;

            case
'file': // valid file types: image, video, audio, other
               
$valid_file_types = array( 'image', 'video', 'audio', 'other' );
                if(
in_array( $File->get_file_type(), $valid_file_types ) )
                {
                    if( ! empty(
$inline[3] ) ) // check if second colon is present
                   
{
                       
// Get the file caption
                       
$caption = $inline[4];

                        if( ! empty(
$caption ) )
                        {
// Caption is set
                           
$current_file_params['title'] = strip_tags( $caption );
                        }
                    }

                    if( empty(
$current_file_params['title'] ) )
                    {
// Use real file name as title when it is not defined for inline tag
                       
$file_title = $File->get( 'title' );
                       
$current_file_params['title'] = ' '.( empty( $file_title ) ? $File->get_name() : $file_title );
                    }
                    elseif(
$current_file_params['title'] == '-' )
                    {
// Don't display a title in this case, Only file icon will be displayed
                       
$current_file_params['title'] = '';
                    }
                    else
                    {
// Add a space between file icon and title
                       
$current_file_params['title'] = ' '.$current_file_params['title'];
                    }

                   
$inlines[$current_inline] = '<a href="'.$File->get_url().'"'
                       
.( empty( $current_file_params['class'] ) ? '' : ' class="'.$current_file_params['class'].'"' )
                        .
'>'.$File->get_icon( $current_file_params ).$current_file_params['title'].'</a>';
                }
                else
                {
// not a valid file type, do not process
                   
$inlines[$current_inline] = $current_inline;
                }
                break;

            case
'video':    // valid file type: video
               
if( $File->is_video() )
                {
                   
$current_video_params = $params;
                   
// Create an empty dummy element where the plugin is expected to append the rendered video
                   
$current_video_params['data'] = '';

                    foreach(
$current_video_params as $param_key => $param_value )
                    {
// Pass all params by reference, in order to give possibility to modify them by plugin
                        // So plugins can add some data before/after tags (E.g. used by infodots plugin)
                       
$current_video_params[ $param_key ] = & $current_video_params[ $param_key ];
                    }

                   
// Prepare params before rendering attachment:
                   
$Plugins->trigger_event_first_true_with_params( $prepare_plugin_event_name, $current_video_params );

                   
// Render attachments by plugin:
                   
if( count( $Plugins->trigger_event_first_true_with_params( $render_plugin_event_name, $current_video_params ) ) != 0 )
                    {    
// This attachment has been rendered by a plugin (to $current_video_params['data']):
                       
$inlines[$current_inline] = $current_video_params['data'];
                    }
                    else
                    {
// no plugin available or was able to render the tag
                       
$inlines[$current_inline] = $current_inline;
                    }
                }
                else
                {
// not a video file, do not process
                   
$inlines[$current_inline] = $current_inline;
                }
                break;

            case
'audio': // valid file type: audio
               
if( $File->is_audio() )
                {
                   
$current_audio_params = $params;
                   
// Create an empty dummy element where the plugin is expected to append the rendered video
                   
$current_audio_params['data'] = '';

                    foreach(
$current_audio_params as $param_key => $param_value )
                    {
// Pass all params by reference, in order to give possibility to modify them by plugin
                        // So plugins can add some data before/after tags (E.g. used by infodots plugin)
                       
$current_audio_params[ $param_key ] = & $current_audio_params[ $param_key ];
                    }

                   
// Prepare params before rendering attachment:
                   
$Plugins->trigger_event_first_true_with_params( $prepare_plugin_event_name, $current_audio_params );

                   
// Render attachments by plugin:
                   
if( count( $Plugins->trigger_event_first_true_with_params( $render_plugin_event_name, $current_audio_params ) ) != 0 )
                    {    
// This attachment has been rendered by a plugin (to $current_audio_params['data']):
                       
$inlines[$current_inline] = $current_audio_params['data'];
                    }
                    else
                    {
// no plugin available or was able to render the tag
                       
$inlines[$current_inline] = $current_inline;
                    }
                }
                else
                {
// not a video file, do not process
                   
$inlines[$current_inline] = $current_inline;
                }
                break;

            case
'folder':
                if(
$File->is_dir() )
                {
                   
$current_folder_params = $params;

                    if( ! empty(
$inline[3] ) ) // check if second colon is present
                   
{
                        if(
preg_match( '/^\d+$/', $inline[4] ) )
                        {
// limit number of images
                           
$current_folder_params['gallery_image_limit'] = (int) $inline[4];
                        }
                    }

                   
$inlines[$current_inline] = $File->get_gallery( $current_folder_params );
                }
                else
                {
                   
$inlines[$current_inline] = $current_inline;
                }
                break;

            default:
               
$inlines[$current_inline] = $current_inline;
        }
    }

    return
$inlines;
}


/**
 * Convert date format from locale for jQuery datepicker plugin
 *
 * @param string Date format of locale from DB; for example: Y-m-d
 * @return string Date format for jQuery datepicker plugin; for example: yy-mm-dd
 */
function php_to_jquery_date_format( $php_format )
{
   
$tokens = array(
           
// Day
           
'd' => 'dd',
           
'D' => 'D',
           
'j' => 'd',
           
'l' => 'DD',
           
'N' => '',
           
'S' => '',
           
'w' => '',
           
'z' => 'o',
           
// Week
           
'W' => '',
           
// Month
           
'F' => 'MM',
           
'm' => 'mm',
           
'M' => 'M',
           
'n' => 'm',
           
't' => '',
           
// Year
           
'L' => '',
           
'o' => '',
           
'Y' => 'yy',
           
'y' => 'y',
           
// Time
           
'a' => '',
           
'A' => '',
           
'B' => '',
           
'g' => '',
           
'G' => '',
           
'h' => '',
           
'H' => '',
           
'i' => '',
           
's' => '',
           
'u' => '' );

   
$js_format = "";
   
$escaping = false;
    for(
$i = 0; $i < strlen( $php_format ); $i++ )
    {
       
$char = $php_format[$i];
        if(
$char === '\\') // PHP date format escaping character
       
{
           
$i++;
            if(
$escaping ) $js_format .= $php_format[$i];
            else
$js_format .= '\'' . $php_format[$i];
           
$escaping = true;
        }
        else
        {
            if(
$escaping )
            {
               
$jqueryui_format .= "'"; $escaping = false;
            }
            if( isset(
$tokens[$char] ) )
            {
               
$js_format .= $tokens[$char];
            }
            else
            {
               
$js_format .= $char;
            }
        }
    }

    return
$js_format;
}


/**
 * Check if given string is HTML
 *
 * @param string String to check if HTML
 * @return boolean True if string is HTML
 */
function is_html( $string )
{
    return
$string != strip_tags( $string ) ? true : false;
}


/**
 * Get date format from current locale for jQuery datepicker plugin
 *
 * @return string Date format; for example: yy-mm-dd
 */
function jquery_datepicker_datefmt()
{
    return
php_to_jquery_date_format( locale_input_datefmt() );
}


/**
 * Get month names as string of JavaScript array for jQuery datepicker plugin
 *
 * @return string
 */
function jquery_datepicker_month_names()
{
   
$months = array(
           
TS_('January'),
           
TS_('February'),
           
TS_('March'),
           
TS_('April'),
           
TS_('May'),
           
TS_('June'),
           
TS_('July'),
           
TS_('August'),
           
TS_('September'),
           
TS_('October'),
           
TS_('November'),
           
TS_('December')
        );

    return
'[\''.implode( '\', \'', $months ).'\']';
}


/**
 * Get week day names as string of JavaScript array for jQuery datepicker plugin
 *
 * @return string
 */
function jquery_datepicker_day_names()
{
   
$days = array(
           
TS_('Sun'),
           
TS_('Mon'),
           
TS_('Tue'),
           
TS_('Wed'),
           
TS_('Thu'),
           
TS_('Fri'),
           
TS_('Sat')
        );

    foreach(
$days as $d => $day )
    {
       
$days[ $d ] = utf8_substr( $day, 0, 2 );
    }

    return
'[\''.implode( '\', \'', $days ).'\']';
}


/**
 * Find the dates without data and fill them with 0 to display on graph and table
 *
 * @param array Source data
 * @param array Default data, e.g. array( 'hits' => 0 )
 * @param string Start date of log in format 'YYYY-mm-dd'
 * @param string End date of log in format 'YYYY-mm-dd'
 * @return array Fixed data
 */
function fill_empty_days( $data, $default_data, $start_date, $end_date )
{
   
$fixed_data = array();
   
$start_date = date( 'Y-n-j', strtotime( $start_date) );
   
$end_date = date( 'Y-n-j', strtotime( $end_date) );

    if( empty(
$data ) )
    {
        return
$fixed_data;
    }

   
// Get additional fields which must be exist in each array item of new filled empty day below:
   
$additional_fields = array_diff_key( $data[0], array( 'year' => 0, 'month' => 0, 'day' => 0 ) );

   
// Check if data array contains start and end dates:
   
$start_date_is_contained = empty( $start_date );
   
$end_date_is_contained = empty( $end_date );

    if( !
$start_date_is_contained || ! $end_date_is_contained )
    {
        foreach(
$data as $row )
        {
           
$this_date = $row['year'].'-'.$row['month'].'-'.$row['day'];
            if(
$this_date == $start_date )
            {    
// The start date is detected:
               
$start_date_is_contained = true;
            }
            if(
$this_date == $end_date )
            {    
// The start date is detected:
               
$end_date_is_contained = true;
            }
            if(
$start_date_is_contained && $end_date_is_contained )
            {    
// Stop array searching here because we have found the dates:
               
break;
            }
        }
    }

    if( !
$start_date_is_contained )
    {    
// Add item to array with 0 for start date if stats has no data for the date:
       
array_push( $data, array_merge( array(
               
'year'     => date( 'Y', strtotime( $start_date ) ),
               
'month'    => date( 'n', strtotime( $start_date ) ),
               
'day'      => date( 'j', strtotime( $start_date ) ),
        ),
$default_data ) + $additional_fields );
    }
    if( !
$end_date_is_contained )
    {    
// Add item to array with 0 for end date if stats has no data for the date:
       
array_unshift( $data, array_merge( array(
               
'year'     => date( 'Y', strtotime( $end_date ) ),
               
'month'    => date( 'n', strtotime( $end_date ) ),
               
'day'      => date( 'j', strtotime( $end_date ) ),
        ),
$default_data ) + $additional_fields );
    }

    foreach(
$data as $row )
    {
       
$this_date = $row['year'].'-'.$row['month'].'-'.$row['day'];

        if( isset(
$prev_date ) && $prev_date != $this_date )
        {    
// If data are from another day:
           
$prev_time = strtotime( $prev_date ) - 86400;
           
$this_time = strtotime( $this_date );

            if(
$prev_time != $this_time )
            {    
// If previous date is not previous day(it means some day has no data):
               
$empty_days = ( $prev_time - $this_time ) / 86400;
                for(
$d = 0; $d < $empty_days; $d++ )
                {    
// Add each empty day to array with default data:
                   
$empty_day = $prev_time - $d * 86400;
                   
$fixed_data[] = array_merge( array(
                           
'year'     => date( 'Y', $empty_day ),
                           
'month'    => date( 'n', $empty_day ),
                           
'day'      => date( 'j', $empty_day ),
                    ),
$default_data ) + $additional_fields;
                }
            }
        }

       
$prev_date = $row['year'].'-'.$row['month'].'-'.$row['day'];
       
$fixed_data[] = $row;
    }

    return
$fixed_data;
}


/**
 * Get image file used for social media
 *
 * @param object Item object
 * @param array Params
 * @return object Image File or Link object
 */
function get_social_media_image( $Item = NULL, $params = array() )
{
   
$params = array_merge( array(
           
'use_item_cat_fallback'      => true,
           
'use_coll_fallback'          => true,
           
'use_site_fallback'          => true,
           
'return_as_link'             => false,
        ),
$params );

   
$social_media_image = NULL;

    if( ! empty(
$Item ) )
    {    
// Try to get attached images
       
$LinkOwner = new LinkItem( $Item );
        if(  
$LinkList = $LinkOwner->get_attachment_LinkList( 1000, 'cover,background,teaser,teaserperm,teaserlink', 'image', array(
               
'sql_select_add' => ', CASE WHEN link_position = "cover" THEN 1 WHEN link_position IN ( "teaser", "teaserperm", "teaserlink" ) THEN 2 ELSE 3 END AS link_priority',
               
'sql_order_by'   => 'link_priority ASC, link_order ASC' ) ) )
        {    
// Item has linked files:
           
while( $Link = & $LinkList->get_next() )
            {
                if( ! (
$File = & $Link->get_File() ) )
                {    
// No File object:
                   
global $Debuglog;
                   
$Debuglog->add( sprintf( 'Link ID#%d of item #%d does not have a file object!', $Link->ID, $Item->ID ), array( 'error', 'files' ) );
                    continue;
                }

                if( !
$File->exists() )
                {    
// File doesn't exist:
                   
global $Debuglog;
                   
$Debuglog->add( sprintf( 'File linked to item #%d does not exist (%s)!', $Item->ID, $File->get_full_path() ), array( 'error', 'files' ) );
                    continue;
                }

                if(
$File->is_image() )
                {    
// Use only image files for og:image tag:
                   
if( $params['return_as_link'] )
                    {
                        return
$Link;
                    }
                    else
                    {
                        return
$File;
                    }
                    break;
                }
            }
        }

        if(
$params['use_item_cat_fallback'] )
        {    
// No attached image from Item, let's try getting one from the Item's default chapter
           
$FileCache = & get_FileCache();
            if(
$default_Chapter = & $Item->get_main_Chapter() )
            {    
// Try social media boilerplate image first:
               
$social_media_image_file_ID = $default_Chapter->get( 'social_media_image_file_ID', false );
                if(
$social_media_image_file_ID > 0 && ( $File = & $FileCache->get_by_ID( $social_media_image_file_ID  ) ) && $File->is_image() )
                {
                    return
$File;
                }
            }
        }
    }

    global
$Blog;

    if(
$params['use_coll_fallback'] && $Blog )
    {    
// Try to get collection social media boiler plate and collection image/logo:
       
$FileCache = & get_FileCache();
       
$social_media_image_file_ID = $Blog->get_setting( 'social_media_image_file_ID', false );
        if(
$social_media_image_file_ID > 0 && ( $File = & $FileCache->get_by_ID( $social_media_image_file_ID  ) ) && $File->is_image() )
        {    
// Try social media boiler plate first:
           
return $File;
        }
    }

    if(
$params['use_site_fallback'] )
    {    
// Use social media boilerplate logo if configured
       
global $Settings;

       
$FileCache = & get_FileCache();
       
$social_media_image_file_ID = intval( $Settings->get( 'social_media_image_file_ID' ) );
        if(
$social_media_image_file_ID > 0 && ( $File = $FileCache->get_by_ID( $social_media_image_file_ID, false ) ) && $File->is_image() )
        {
            return
$File;
        }
    }

    return
NULL;
}


/**
 * Opens modal to insert inline image tags.
 * Used by the following plugins:
 * - evo_TinyMCE
 * - evo_inlines
 *
 * @param array Params
 */
function insert_image_links_block( $params )
{
    global
$current_User, $inc_path, $Blog, $blog, $LinkOwner;
    global
$is_admin_page;

   
load_funcs( 'links/model/_link.funcs.php' );

   
$params = array_merge( array(
           
'target_type' => NULL,
        ),
$params );

    if( ! empty(
$params['blog'] ) )
    {
       
$BlogCache = & get_BlogCache();
       
$blog = $params['blog'];
       
$Blog = $BlogCache->get_by_ID( $blog );
    }

   
$temp_ID = empty( $params['temp_ID'] ) ? NULL : $params['temp_ID'];
   
$is_admin_page = is_logged_in() && isset( $params['request_from'] ) && ( $params['request_from'] == 'back' );

    switch(
$params['target_type'] )
    {
        case
'Item':
            if( ! isset(
$params['target_ID'] ) && ! isset( $params['temp_ID'] ) )
            {
                return;
            }

           
$ItemCache = & get_ItemCache();
           
$edited_Item = & $ItemCache->get_by_ID( $params['target_ID'], false, false );

            if( empty(
$blog ) && $edited_Item )
            {
               
$Blog = $edited_Item->get_Blog();
               
$blog = $Blog->ID;
            }

            if( isset(
$GLOBALS['files_Module'] )
                && ( (
$edited_Item && check_user_perm( 'item_post!CURSTATUS', 'edit', false, $edited_Item ) ) || ( empty( $edited_Item ) && $params['temp_ID'] ) )
                &&
check_user_perm( 'files', 'view', false ) )
            {    
// Files module is enabled, but in case of creating new posts we should show file attachments block only if user has all required permissions to attach files
               
load_class( 'links/model/_linkitem.class.php', 'LinkItem' );
                global
$LinkOwner; // Initialize this object as global because this is used in many link functions
               
$LinkOwner = new LinkItem( $edited_Item, $temp_ID );
            }
            break;

        case
'Comment':
            if( ! isset(
$params['target_ID'] ) )
            {
                return;
            }

           
$CommentCache = & get_CommentCache();
           
$edited_Comment = & $CommentCache->get_by_ID( $params['target_ID'] );
           
$comment_Item = & $edited_Comment->get_Item();

            if( empty(
$blog ) && $comment_Item )
            {
               
$Blog = $comment_Item->get_Blog();
               
$blog = $Blog->ID;
            }

            if( isset(
$GLOBALS['files_Module'] )
                &&
check_user_perm( 'comment!CURSTATUS', 'edit', false, $edited_Comment )
                &&
check_user_perm( 'files', 'view', false ) )
            {    
// Files module is enabled, but in case of creating new comments we should show file attachments block only if user has all required permissions to attach files
               
load_class( 'links/model/_linkcomment.class.php', 'LinkComment' );
                global
$LinkOwner; // Initialize this object as global because this is used in many link functions
               
$LinkOwner = new LinkComment( $edited_Comment );
            }
            break;

        case
'EmailCampaign':
            if( ! isset(
$params['target_ID'] ) )
            {
                return;
            }

           
$EmailCampaignCache = & get_EmailCampaignCache();
           
$edited_EmailCampaign = $EmailCampaignCache->get_by_ID( $params['target_ID'] );

            if( isset(
$GLOBALS['files_Module'] )
                &&
check_user_perm( 'emails', 'edit', false )
                &&
check_user_perm( 'files', 'view', false ) )
            {    
// Files module is enabled, but in case of creating new email campaign  we should show file attachments block only if user has all required permissions to attach files
               
load_class( 'links/model/_linkemailcampaign.class.php', 'LinkEmailCampaign' );
                global
$LinkOwner; // Initialize this object as global because this is used in many link functions
               
$LinkOwner = new LinkEmailCampaign( $edited_EmailCampaign );
            }
            break;

        case
'Message':
            if( ! isset(
$params['target_ID'] ) && ! isset( $params['temp_ID'] ) )
            {
                return;
            }

           
$MessageCache = & get_MessageCache();
           
$edited_Message = $MessageCache->get_by_ID( $params['target_ID'], false, false );

            if( isset(
$GLOBALS['files_Module'] )
                &&
check_user_perm( 'perm_messaging', 'reply' )
                &&
check_user_perm( 'files', 'view', false ) )
            {    
// Files module is enabled, but in case of creating new messages we should show file attachments block only if user has all required permissions to attach files
               
load_class( 'links/model/_linkmessage.class.php', 'LinkMessage' );
                global
$LinkOwner; // Initialize this object as global because this is used in many link functions
               
$LinkOwner = new LinkMessage( $edited_Message, $temp_ID );
            }
            break;

        default:
            return;
    }

    global
$fm_mode;
   
$fm_mode = 'file_select';
   
// Set a different drag and drop fieldset prefix:
   
$fieldset_prefix = 'modal_';

    if(
is_admin_page() )
    {
        global
$UserSettings, $adminskins_path, $AdminUI;

       
$admin_skin = $UserSettings->get( 'admin_skin', $current_User->ID );
        require_once
$adminskins_path.$admin_skin.'/_adminUI.class.php';
       
$AdminUI = new AdminUI();
    }
    else
    {
        global
$Skin, $inc_path;

       
init_fontawesome_icons();

       
$blog_skin_ID = $Blog->get_skin_ID();
       
$SkinCache = & get_SkinCache();
       
$Skin = & $SkinCache->get_by_ID( $blog_skin_ID );
    }

    require
$inc_path.'links/views/_link_list.view.php';
}


/**
 * Get line for CSV file from provided array
 *
 * @param array Row data
 * @param string Delimiter
 * @param string Enclosure
 * @param string End of line
 * @return string
 */
function get_csv_line( $row, $delimiter = ';', $enclosure = '"', $eol = "\n" )
{
    foreach(
$row as $r => $cell )
    {
       
$row[ $r ] = str_replace( $enclosure, $enclosure.$enclosure, $cell );
        if(
strpos( $cell, $delimiter ) !== false )
        {
           
$row[ $r ] = $enclosure.$row[ $r ].$enclosure;
        }
    }

    return
implode( $delimiter, $row ).$eol;
}


/**
 * Display a panel to upload files before import
 *
 * @param array Params
 * @return array Already uploaded files in the requested folder
 */
function display_importer_upload_panel( $params = array() )
{
    global
$admin_url, $media_path;

   
$params = array_merge( array(
           
'folder'                 => '',
           
'allowed_extensions'     => 'csv', // Allowed extensions to import, separated by |
           
'infolder_extensions'    => false, // Allowed extensions inside folders, separated by |, FALSE - to don't find files in subfolders
           
'folder_with_extensions' => false, // Find folders which contain at least one file with extensions(separated by |) in subfolders recursively
           
'find_attachments'       => false,
           
'display_type'           => false,
           
'help_slug'              => '',
           
'refresh_url'            => '',
        ),
$params );

   
evo_flush();

   
// Get available files to import from the folder /media/import/
   
$import_files = get_import_files( $params['folder'], $params['allowed_extensions'], $params['infolder_extensions'], $params['find_attachments'], $params['folder_with_extensions'] );

   
load_class( '_core/ui/_table.class.php', 'Table' );
   
$Table = new Table( NULL, 'import' );

   
$Table->cols = array();
   
$Table->cols[] = array( 'th' => T_('Import'), 'td_class' => 'shrinkwrap' );
   
$Table->cols[] = array( 'th' => T_('File') );
    if(
$params['display_type'] )
    {    
// Display file type:
       
$Table->cols[] = array( 'th' => T_('Type') );
    }
   
$Table->cols[] = array( 'th' => T_('Date'), 'td_class' => 'shrinkwrap' );

   
// Get link to manual page:
   
$manual_link = ( empty( $params['help_slug'] ) ? '' : get_manual_link( $params['help_slug'] ) );

   
$Table->title = T_('Potential files to be imported').$manual_link;
    if( ! empty(
$params['refresh_url'] ) )
    {    
// Display a link to refresh the uploaded files:
       
$Table->title .= ' - '.action_icon( T_('Refresh'), 'refresh', $params['refresh_url'], T_('Refresh'), 3, 4 );
    }

   
$FileRootCache = & get_FileRootCache();
   
$FileRoot = & $FileRootCache->get_by_type_and_ID( 'import', '0', true );
   
$import_perm_view = check_user_perm( 'files', 'view', false, $FileRoot );
    if(
$import_perm_view )
    {
// Current user must has access to the import dir
       
if( check_user_perm( 'files', 'edit_allowed', false, $FileRoot ) )
        {
// User has full access
           
$import_title = T_('Upload/Manage import files');
        }
        else if(
check_user_perm( 'files', 'add', false, $FileRoot ) )
        {
// User can only upload the files to import root
           
$import_title = T_('Upload import files');
        }
        else
        {
// Only view
           
$import_title = T_('View import files');
        }
       
$Table->title .= ' - '
           
.action_icon( $import_title, 'folder', $admin_url.'?ctrl=files&amp;root=import_0&amp;path='.$params['folder'], $import_title, 3, 4,
                array(
'onclick' => 'return import_files_window()' )
            ).
' <span class="note">(popup)</span>';
    }
   
$Table->display_init();

    echo
$Table->params['before'];

   
// TITLE:
   
$Table->display_head();

    if( empty(
$import_files ) )
    {    
// No files to import:
       
$Table->total_pages = 0;
       
$Table->no_results_text = '<div class="center">'.T_('We have not found any suitable file to perform the import. Please read the details at the manual page.').$manual_link.'</div>';

       
// BODY START:
       
$Table->display_body_start();
       
$Table->display_list_start();
       
$Table->display_list_end();
       
// BODY END:
       
$Table->display_body_end();
    }
    else
    {    
// Display the files to import in table:

        // TABLE START:
       
$Table->display_list_start();

       
// COLUMN HEADERS:
       
$Table->display_col_headers();
       
// BODY START:
       
$Table->display_body_start();

        foreach(
$import_files as $import_file )
        {
           
$Table->display_line_start();

           
// Checkbox to import
           
$Table->display_col_start();
            echo
'<input type="radio" name="import_file" value="'.$import_file['path'].'"'.( get_param( 'import_file' ) == $import_file['path'] ? ' checked="checked"' : '' ).' />';
           
$Table->display_col_end();

           
// File
           
$Table->display_col_start();
            echo
$import_file['name'];
           
$Table->display_col_end();

           
// Type
           
if( $params['display_type'] )
            {    
// Display file type:
               
$Table->display_col_start();
                echo
$import_file['type'];
               
$Table->display_col_end();
            }

           
// File date
           
$Table->display_col_start();
            echo
date( locale_datefmt().' '.locale_timefmt(), $import_file['date'] );
           
$Table->display_col_end();

           
$Table->display_line_end();

           
evo_flush();
        }

       
// BODY END:
       
$Table->display_body_end();

       
// TABLE END:
       
$Table->display_list_end();
    }

    echo
$Table->params['after'];

?>
<script>
jQuery( '.table_scroll td' ).on( 'click', function()
{
    jQuery( this ).parent().find( 'input[type=radio]' ).prop( 'checked', true );
} );
</script>
<?php
   
if( $import_perm_view )
    {    
// Current user must has access to the import dir:

        // Initialize JavaScript to build and open window:
       
echo_modalwindow_js();
?>
<script>
function import_files_window()
{
    openModalWindow( '<span class="loader_img absolute_center" title="<?php echo T_('Loading...'); ?>"></span>',
        '90%', '80%', true, '<?php echo TS_('Add/Link files'); ?>', '', true );
    jQuery.ajax(
    {
        type: 'POST',
        url: '<?php echo get_htsrv_url(); ?>async.php',
        data:
        {
            'action': 'import_files',
            'path': '<?php echo $params['folder']; ?>',
            'crumb_import': '<?php echo get_crumb( 'import' ); ?>',
        },
        success: function( result )
        {
            openModalWindow( result, '90%', '80%', true, '<?php echo TS_('Upload/Manage import files'); ?>', '' );
        }
    } );
    return false;
}

jQuery( document ).on( 'click', '#modal_window button[data-dismiss=modal]', function()
{    // Reload page on closing modal window to display new uploaded files:
    location.reload();
} );
</script>
<?php
   
}

    return
$import_files;
}


/**
 * Get available files to import from the requested folder
 *
 * @param string Sub folder in the folder /media/import/
 * @param string Allowed extensions to import, separated by |
 * @param string|boolean Allowed extensions inside folders, separated by |, FALSE - to don't find files in subfolders
 * @param boolean TRUE - to find folder of attachments
 * @param string|boolean Find folders which contain at least one file with extensions(separated by |) recursively in all subfolders
 * @return array Files
 */
function get_import_files( $folder = '', $allowed_extensions = 'xml|txt|zip', $infolder_extensions = 'xml|txt', $find_attachments = true, $folder_with_extensions = false )
{
    global
$media_path;

   
// Get all files from the import folder:
   
$root_path = $media_path.'import/'.( empty( $folder ) ? '' : $folder.'/' );
   
$files = get_filenames( $root_path, array(
           
'flat' => false
       
) );

   
$import_files = array();

    if( empty(
$files ) )
    {
// No access to the import folder OR it is empty
       
return $import_files;
    }

   
$file_paths = array();
    foreach(
$files as $folder_name => $file )
    {
        if(
is_array( $file ) )
        {    
// It is a folder
           
if( $infolder_extensions !== false )
            {    
// Find files inside:
               
foreach( $file as $key => $sub_file )
                {
                    if(
is_string( $sub_file ) && preg_match( '/\.('.$infolder_extensions.')$/i', $sub_file, $file_matches ) )
                    {
                       
$file_paths[] = array( $sub_file, $file_matches[1] );
                    }
                }
            }
            if(
$folder_with_extensions !== false && check_folder_with_extensions( $root_path.$folder_name, $folder_with_extensions ) )
            {    
// Use full folder as single import pack when it contains file with requested extensions:
               
$file_paths[] = array( $root_path.$folder_name, '$dir$' );
            }
        }
        elseif(
is_string( $file ) && preg_match( '/\.('.$allowed_extensions.')$/i', $file, $file_matches ) )
        {    
// File in the root:
           
$file_paths[] = array( $file, $file_matches[1] );
        }
    }

   
$media_path_length = strlen( $media_path.'import/'.( empty( $folder ) ? '' : $folder.'/' ) );

    foreach(
$file_paths as $file_data )
    {
        switch(
$file_data[1] )
        {
            case
'$dir$':
               
$file_type = T_('Folder');
                break;

            case
'zip':
               
$file_type = T_('Compressed Archive');
                break;

            default:
                if(
$find_attachments && ( $file_attachments_folder = get_import_attachments_folder( $file_data[0] ) ) )
                {    
// Probably it is a file with attachments folder:
                   
$file_type = sprintf( T_('Complete export (attachments folder: %s)'), '<code>'.substr( $file_attachments_folder, strlen( dirname( $file_data[0] ) ) + 1, -1 ).'</code>' );
                }
                else
                {    
// Single XML file without attachments folder:
                   
$file_type = T_('Basic export').( $find_attachments ? ' ('.T_('no attachments folder found').')' : '' );
                }
                break;
        }

       
$import_files[] = array(
               
'path' => $file_data[0],
               
'name' => substr( $file_data[0], $media_path_length ),
               
'type' => $file_type,
               
'date' => filemtime( $file_data[0] ),
            );
    }

   
// Sort import files by date DESC:
   
usort( $import_files, 'sort_import_files_callback' );

    return
$import_files;
}


/**
 * Callback function to sort import files by date DESC
 *
 * @param array Import file data
 * @param array Import file data
 * @return boolean
 */
function sort_import_files_callback( $a, $b )
{
    if(
$a['date'] == $b['date'] )
    {    
// Sort by file name with same dates:
       
return $a['name'] < $b['name'] ? -1 : 1;
    }

    return (
$a['date'] > $b['date'] ? -1 : 1 );
}


/**
 * Find attachments folder path for given import file path
 *
 * @param string File path
 * @param boolean TRUE to use first found folder if no reserved folders not found before
 * @return string Folder path
 */
function get_import_attachments_folder( $file_path, $first_folder = false )
{
   
$file_name = basename( $file_path );
   
$file_folder_path = dirname( $file_path ).'/';
   
$folder_full_name = preg_replace( '#\.[^\.]+$#', '', $file_name );
   
$folder_part_name = preg_replace( '#_[^_]+$#', '', $folder_full_name );

   
// Find and get first existing folder with attachments:
   
if( is_dir( $file_folder_path.$folder_full_name ) )
    {    
// 1st priority folder:
       
return $file_folder_path.$folder_full_name.'/';
    }
    if(
is_dir( $file_folder_path.$folder_part_name.'_files' ) )
    {    
// 2nd priority folder:
       
return $file_folder_path.$folder_part_name.'_files/';
    }
    if(
is_dir( $file_folder_path.$folder_part_name.'_attachments' ) )
    {    
// 3rd priority folder:
       
return $file_folder_path.$folder_part_name.'_attachments/';
    }
    if(
is_dir( $file_folder_path.'b2evolution_export_files' ) )
    {    
// 4th priority folder:
       
return $file_folder_path.'b2evolution_export_files/';
    }
    if(
is_dir( $file_folder_path.'export_files' ) )
    {    
// 5th priority folder:
       
return $file_folder_path.'export_files/';
    }
    if(
is_dir( $file_folder_path.'import_files' ) )
    {    
// 6th priority folder:
       
return $file_folder_path.'import_files/';
    }
    if(
is_dir( $file_folder_path.'files' ) )
    {    
// 7th priority folder:
       
return $file_folder_path.'files/';
    }
    if(
is_dir( $file_folder_path.'attachments' ) )
    {    
// 8th priority folder:
       
return $file_folder_path.'attachments/';
    }
    if(
is_dir( $file_folder_path.'uploads' ) )
    {    
// 9th priority folder:
       
return $file_folder_path.'uploads/';
    }
    if(
is_dir( $file_folder_path.'wp-content/uploads' ) )
    {    
// 10th priority folder:
       
return $file_folder_path.'wp-content/uploads/';
    }

    if(
$first_folder )
    {    
// Try to use first found folder:
       
$files = scandir( $file_folder_path );
        foreach(
$files as $file )
        {
            if(
$file == '.' || $file == '..' )
            {    
// Skip reserved dir names of the current path:
               
continue;
            }
            if(
is_dir( $file_folder_path.$file ) )
            {    
// 11th priority folder:
               
return $file_folder_path.$file.'/';
            }
        }
    }

   
// File has no attachments folder
   
return false;
}


/**
 * Clear string list of IDs to exclude wrong not ID/number/integer value
 *
 * @param string ID values separated by second param
 * @param string Separator
 * @return string Fixed list
 */
function clear_ids_list( $ids_list, $separator = ',' )
{
    if(
$ids_list === '' )
    {    
// Empty list:
       
return $ids_list;
    }

    if(
strpos( $ids_list, '-' ) === 0 )
    {    
// Remove first '-' char from start, which is used for exluding list:
       
$ids_list = substr( $ids_list, 1 );
    }

   
$ids_list = explode( $separator, $ids_list );

    foreach(
$ids_list as $i => $ID )
    {
        if( !
is_number( $ID ) )
        {    
// Remove a not number value from list:
           
unset( $ids_list[ $i ] );
        }
    }

    return
implode( $separator, $ids_list );
}


/**
 * Check if the given allowed
 *
 * @param string The options which should be checked
 * @param string String of options separated by comma, '*' - allow all options, use '-' before options if they should be excluded/denied
 * @return boolean
 */
function is_allowed_option( $checked_option, $allowed_options )
{
    if(
$allowed_options === '*' )
    {    
// All options are allowed:
       
return true;
    }

   
$is_allowed_options = true;
   
$allowed_options = $allowed_options;
    if(
substr( $allowed_options, 0, 1 ) == '-' )
    {    
// The options should excluded/denied:
       
$allowed_options = substr( $allowed_options, 1 );
       
$is_allowed_options = false;
    }
   
$allowed_options = explode( ',', $allowed_options );
   
$is_in_checked_array = in_array( $checked_option, $allowed_options );

    return (
$is_allowed_options ? $is_in_checked_array : ! $is_in_checked_array );
}


/**
 * Converts path into an array
 *
 * @param string Path usually in dot notation
 * @param string Value of the path property
 * @param string Separator string
 * @return array Schema property array
 */
function convert_path_to_array( $property, $value, $separator = '.' )
{
   
$output = NULL;

    foreach(
array_reverse( explode( $separator, $property ) ) as $key )
    {
        if( empty(
$output ) )
        {
           
$output = array( $key => $value );
        }
        else
        {
           
$output = array( $key => $output );
        }
    }

    return
$output;
}


/**
 * Get customizer url
 *
 * @param object Collection
 * @return string Customizer URL
 */
function get_customizer_url( $url_Blog = NULL )
{
    global
$customizer_relative_url, $Blog;

    if(
$url_Blog === NULL && isset( $Blog ) )
    {    
// Use current collection:
       
$url_Blog = $Blog;
    }

    return
$url_Blog->get_baseurl_root().$customizer_relative_url;
}


/**
 * Convert operator alias to jQuery QueryBuilder format
 *
 * @param string Alias Operator
 * @return string Query Builder Operator
 */
function get_querybuilder_operator( $operator )
{
    switch(
$operator )
    {
        case
'=':
            return
'equal';
        case
'!=':
        case
'<>':
            return
'not_equal';
        case
'<':
            return
'less';
        case
'<=':
            return
'less_or_equal';
        case
'>':
            return
'greater';
        case
'>=':
            return
'greater_or_equal';
        default:
            return
$operator;
    }
}


/**
 * Temporary function to check if we should use defer when loading scripts.
 */
function use_defer()
{
    global
$disp, $ReqPath,
       
$use_defer,
       
$use_defer_for_backoffice,
       
$use_defer_for_loggedin_users,
       
$use_defer_for_anonymous_users,
       
$use_defer_for_anonymous_disp_register,
       
$use_defer_for_anonymous_disp_register_finish,
       
$use_defer_for_anonymous_disp_users,
       
$use_defer_for_anonymous_disp_anonpost,
       
$use_defer_for_loggedin_disp_single_page,
       
$use_defer_for_loggedin_disp_front,
       
$use_defer_for_loggedin_disp_profile,
       
$use_defer_for_loggedin_disp_pwdchange,
       
$use_defer_for_loggedin_disp_edit,
       
$use_defer_for_loggedin_disp_proposechange,
       
$use_defer_for_loggedin_disp_edit_comment,
       
$use_defer_for_loggedin_disp_comments,
       
$use_defer_for_loggedin_disp_visits,
       
$use_defer_for_loggedin_disp_messages,
       
$use_defer_for_loggedin_disp_threads,
       
$use_defer_for_loggedin_disp_users,
       
$use_defer_for_loggedin_disp_contacts,
       
$use_defer_for_default_register_form;
   
   
$r =  $use_defer
           
&& ( is_admin_page() ? $use_defer_for_backoffice : true )
           && (
is_logged_in() ? $use_defer_for_loggedin_users : $use_defer_for_anonymous_users )
           && (
$disp == 'register' ? $use_defer_for_anonymous_disp_register : true )
           && (
$disp == 'register_finish' ? $use_defer_for_anonymous_disp_register_finish : true )
           && (
$disp == 'users' ? $use_defer_for_anonymous_disp_users : true )
           && (
$disp == 'anonpost' ? $use_defer_for_anonymous_disp_anonpost : true )
           && ( empty(
$disp ) && $ReqPath == '/htsrv/register.php' ? $use_defer_for_default_register_form : true )
           && (
is_logged_in() && in_array( $disp, array( 'single', 'page' ) ) ? $use_defer_for_loggedin_disp_single_page : true )
           && (
is_logged_in() && $disp == 'front' ? $use_defer_for_loggedin_disp_front : true )
           && (
is_logged_in() && $disp == 'profile' ? $use_defer_for_loggedin_disp_profile : true )
           && (
is_logged_in() && $disp == 'pwdchange' ? $use_defer_for_loggedin_disp_pwdchange : true )
           && (
is_logged_in() && $disp == 'edit' ? $use_defer_for_loggedin_disp_edit : true )
           && (
is_logged_in() && $disp == 'proposechange' ? $use_defer_for_loggedin_disp_proposechange : true )
           && (
is_logged_in() && $disp == 'edit_comment' ? $use_defer_for_loggedin_disp_edit_comment : true )
           && (
is_logged_in() && $disp == 'comments' ? $use_defer_for_loggedin_disp_comments : true )
           && (
is_logged_in() && $disp == 'visits' ? $use_defer_for_loggedin_disp_visits : true )
           && (
is_logged_in() && $disp == 'messages' ? $use_defer_for_loggedin_disp_messages : true )
           && (
is_logged_in() && $disp == 'threads' ? $use_defer_for_loggedin_disp_threads : true )
           && (
is_logged_in() && $disp == 'users' ? $use_defer_for_loggedin_disp_users : true )
           && (
is_logged_in() && $disp == 'contacts' ? $use_defer_for_loggedin_disp_contacts : true );

    return
$r;
}


/**
 * Get rendering error
 *
 * @param string Error message
 * @param string HTML tag: <p>, <span>, <div>
 * @return string
 */
function get_rendering_error( $error_message, $html_tag = 'p' )
{
    if( !
in_array( $html_tag, array( 'p', 'span', 'div' ) ) )
    {    
// Force not allowed html tag:
       
$html_tag = 'p';
    }

    return
'<'.$html_tag.' class="evo_rendering_error">'.$error_message.'</'.$html_tag.'>';
}


/**
 * Display rendering error
 *
 * @param string Error message
 * @param string HTML tag: <p>, <span>, <div>
 */
function display_rendering_error( $error_message, $html_tag = 'p' )
{
    echo
get_rendering_error( $error_message, $html_tag );
}


/**
 * Clean up rendering errors `<p class="evo_rendering_error">...</p>` from provided content
 *
 * @param string Content with rendering error
 * @return string Content without rendering error
 */
function clear_rendering_errors( $content )
{
    return
preg_replace( '#<([a-z]+) class="evo_rendering_error">.+?</\1>#', '', $content );
}
?>