Seditio Source
Root |
./othercms/b2evolution_7.2.3/inc/_core/ui/_table.class.php
<?php
/**
 * This file implements the Table class.
 *
 * This file is part of the evoCore framework - {@link http://evocore.net/}
 * See also {@link https://github.com/b2evolution/b2evolution}.
 *
 * @license GNU GPL v2 - {@link http://b2evolution.net/about/gnu-gpl-license}
 *
 * @copyright (c)2003-2020 by Francois Planque - {@link http://fplanque.com/}
 * Parts of this file are copyright (c)2004-2006 by Daniel HAHLER - {@link http://thequod.de/contact}.
 *
 * @package evocore
 */
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );

load_class( '_core/ui/_uiwidget.class.php', 'Widget' );

/**
 * Class Table
 *
 * @package evocore
 */
class Table extends Widget
{
   
/**
     * Total number of pages
     */
   
var $total_pages = 1;

   
/**
     * Number of cols.
     */
   
var $nb_cols;

   
/**
     * Number of lines already displayed
     */
   
var $displayed_lines_count;

   
/**
     * Number of cols already displayed (in current line)
     */
   
var $displayed_cols_count;

   
/**
     * @var array
     */
   
var $fadeout_array;

    var
$fadeout_count = 0;

   
/**
     * @var boolean
     */
   
var $is_fadeout_line;

    var
$no_results_text;


   
/**
     * URL param names
     */
   
var $param_prefix;


   
/**
     * Parameters for the filter area:
     */
   
var $filter_area;

   
/**
     * Filters preset button that is currently active:
     */
   
var $current_filter_preset = NULL;

   
/**
     * Parameters for the columns select area:
     */
   
var $colselect_area;

   
/**
     * Columns preset button that is currently active:
     */
   
var $current_colselect_preset = NULL;

   
/**
     * Constructor
     *
     * @param string template name to get from $AdminUI
     * @param string prefix to differentiate page/order/filter params
     */
   
function __construct( $ui_template = NULL, $param_prefix = '' )
    {
       
parent::__construct( $ui_template );

       
// Remove unexpected chars from prefix:
       
$this->param_prefix = preg_replace( '#[^a-z0-9\-_]#i', '', $param_prefix );

       
$this->no_results_text = T_('No results').'.';
    }


   
/**
     * Initialize things in order to be ready for displaying.
     *
     * Lazy fills $this->params
     *
     * @param array ***please document***
     * @param array Fadeout settings array( 'key column' => array of values ) or 'session'
     */
   
function display_init( $display_params = NULL, $fadeout = NULL )
    {
        global
$AdminUI, $Session, $Debuglog;

        if( empty(
$this->params ) && isset( $AdminUI ) )
        {
// Use default params from Admin Skin:
           
$this->params = $AdminUI->get_template( 'Results' );
        }

       
// Make sure we have display parameters:
       
if( !is_null($display_params) )
        {
// Use passed params:
            //$this->params = & $display_params;
           
if( !empty( $this->params ) )
            {
               
$this->params = array_merge( $this->params, $display_params );
            }
            else
            {
               
$this->params = & $display_params;
            }
        }

        if( empty(
$this->params ) )
        {
           
$this->params = array();
        }
       
// Initialize default params
       
$this->params = array_merge( array(
               
// If button at top right: (default values for v5 skins)
               
'filter_button_before' => '',
               
'filter_button_after'  => '',
               
'filter_button_class'  => 'filter',
               
// If buttom at bottom (only happens in v7+)
               
'bottom_filter_button_before' => '<div class="form-group">',
               
'bottom_filter_button_after'  => '</div>',
               
'bottom_filter_button_class'  => 'evo_btn_apply_filters btn-sm btn-info',
            ),
$this->params );

        if(
$fadeout == 'session' )
        {    
// Get fadeout_array from session:
           
if( ($this->fadeout_array = $Session->get('fadeout_array')) && is_array( $this->fadeout_array ) )
            {
               
$Debuglog->add( 'UIwidget: Got fadeout_array from session data.', 'results' );
               
$Session->delete( 'fadeout_array' );
            }
            else
            {
               
$this->fadeout_array = NULL;
            }
        }
        else
        {
           
$this->fadeout_array = $fadeout;
        }
    }


   
/**
     * Register a filter preset
     *
     * @param string Preset code
     * @param string Preset title
     * @param string Preset URL
     */
   
function register_filter_preset( $preset_codename, $preset_label, $preset_url )
    {
       
// Append a preset
       
$this->filter_area['presets'][$preset_codename] = array(
               
$preset_label,
               
// Append param to indicate current preset:
               
url_add_param( $preset_url, $this->param_prefix.'filter_preset='.$preset_codename ) );
    }


   
/**
     * Display advanced filter fields
     *
     * @param object Form
     */
   
function display_advanced_filter_fields( & $Form )
    {
        if( empty(
$this->filter_area['callback_advanced'] ) )
        {    
// No callback function is defined for advanced filters:
           
return;
        }

       
// Get advanced filters from provided callback function:
       
$func = $this->filter_area['callback_advanced'];
       
$filter_fields = $func( $this->Form );

        if( !
is_array( $filter_fields ) )
        {    
// Don't allow wrong callback for advanced filters:
           
debug_die( 'Callback function <code>'.$this->filter_area['callback_advanced'].'()</code> of advanced filters must has an array in result!' );
        }

        echo
'<div id="evo_results_filters"></div>';
       
$Form->hidden( 'filter_query', '' );

       
$js_filters = array();
        foreach(
$filter_fields as $field_ID => $params )
        {
            if(
$field_ID == '#default' )
            {    
// Skip a reserved field for default filters:
               
continue;
            }

           
$js_filter = array( 'id:\''.$field_ID.'\'' );

            if( isset(
$params['type'] ) )
            {    
// Set default params depending on field type:
               
switch( $params['type'] )
                {
                    case
'date':
                       
$params['operators'] = '=,!=,>,>=,<,<=,between,not_between';
                       
$params['plugin'] = 'datepicker';
                       
$params['plugin_config'] = array(
                           
'dateFormat'  => jquery_datepicker_datefmt(),
                           
'monthNames'  => jquery_datepicker_month_names(),
                           
'dayNamesMin' => jquery_datepicker_day_names(),
                           
'firstDay'    => locale_startofweek(),
                        );
                        if( ! isset(
$params['validation'] ) )
                        {
                           
$params['validation'] = array();
                        }
                       
$params['validation']['format'] = strtoupper( jquery_datepicker_datefmt() );
                        break;
                }
            }

            if( ! isset(
$params['operators'] ) )
            {    
// Use default operator if it is not defined:
               
$params['operators'] = '=,!=';
            }

            foreach(
$params as $param_name => $param_value )
            {
                switch(
$param_name )
                {
                    case
'operators':
                       
// Convert operators to proper format:
                       
if( ! empty( $param_value ) )
                        {
                           
$operators = explode( ',', $param_value );
                            foreach(
$operators as $o => $operator )
                            {    
// Replace aliases with corrent name which is used in jQuery QueryBuilder plugin:
                               
$operators[ $o ] = get_querybuilder_operator( $operator );
                            }
                           
$param_value = '[\''.implode( '\',\'', $operators ).'\']';
                        }
                        break;

                    case
'values':
                       
$param_values = array();
                        foreach(
$param_value as $sub_param_name => $sub_param_value )
                        {
                           
$param_values[] = '{\''.format_to_js( $sub_param_name ).'\':\''.format_to_js( $sub_param_value ).'\'}';
                        }
                       
$param_value = '['.implode( ',', $param_values ).']';
                        break;

                    case
'valueGetter':
                    case
'valueSetter':
                       
// Don't convert these params to string because they are functions:
                       
break;

                    case
'input':
                        if(
strpos( $param_value, 'function' ) === 0 )
                        {    
// Don't convert this param to string if it is a function:
                           
break;
                        }

                    default:
                        if(
is_array( $param_value ) )
                        {    
// Array param:
                           
$param_values = array();
                            foreach(
$param_value as $sub_param_name => $sub_param_value )
                            {
                                if(
$sub_param_value == 'true' || $sub_param_value == 'false' ||
                                   
strpos( $sub_param_value, '[' ) === 0 )
                                {    
// This is a not string value:
                                   
$sub_param_value = $sub_param_value;
                                }
                                else
                                {    
// This is a string value:
                                   
$sub_param_value = '\''.format_to_js( $sub_param_value ).'\'';
                                }
                               
$param_values[] = $sub_param_name.':'.$sub_param_value;
                            }
                           
$param_value = '{'.implode( ',', $param_values ).'}';
                        }
                        else
                        {    
// String param:
                           
$param_value = '\''.format_to_js( $param_value ).'\'';
                        }
                }

               
$js_filter[] = $param_name.':'.$param_value;
            }
           
$js_filters[] = '{'.implode( ',', $js_filter ).'}';
        }

       
// Get filter values from request:
       
$filter_query = param_condition( 'filter_query', '', false, array_keys( $filter_fields ) );
        if( empty(
$filter_query ) || $filter_query === 'null' )
        {    
// Set filter values if no request yet:
           
$filter_query = array(
               
'rules'     => array(),
               
'valid'     => true,
            );
            if( isset(
$filter_fields['#default'] ) )
            {    
// Set filters from default config:
               
foreach( $filter_fields['#default'] as $def_filter_id => $def_filter_data )
                {
                   
$filter_query['rules'][] = array(
                       
'id'       => $def_filter_id,
                       
'operator' => get_querybuilder_operator( is_array( $def_filter_data ) && isset( $def_filter_data[0] ) ? $def_filter_data[0] : $def_filter_data ),
                       
'value'    => is_array( $def_filter_data ) && isset( $def_filter_data[1] ) ? $def_filter_data[1] : '',
                    );
                }
            }
           
$filter_query = json_encode( $filter_query );
        }
?>
<script>
jQuery( document ).ready( function()
{
    jQuery( '#evo_results_filters' ).queryBuilder(
    {
        allow_empty: true,
        display_empty_filter: true,
        plugins: ['bt-tooltip-errors'],
        icons: {
            add_group: 'fa fa-plus-circle',
            add_rule: 'fa fa-plus',
            remove_group: 'fa fa-minus-circle',
            remove_rule: 'fa fa-minus-circle',
            error: 'fa fa-warning',
        },
        operators: [
            'equal', 'not_equal', 'less', 'less_or_equal', 'greater', 'greater_or_equal', 'between', 'not_between', 'contains', 'not_contains',
            { type: 'blank', nb_inputs: 1, multiple: false, apply_to: ['string'] },
            { type: 'user_tagged', nb_inputs: 1, multiple: false, apply_to: ['string'] },
            { type: 'user_not_tagged', nb_inputs: 1, multiple: false, apply_to: ['string'] }
        ],
        lang: {
            add_rule: '<?php echo TS_('Add line'); ?>',
            delete_rule: '<?php echo TS_('Remove line'); ?>',
            add_group: '<?php echo TS_('Add group'); ?>',
            delete_rule: '<?php echo TS_('Remove line'); ?>',
            delete_group: '<?php echo TS_('Remove group'); ?>',
            conditions: {
               AND: 'Match ALL of',
                OR: 'Match ANY of',
            },
            operators: {
                equal: '=',
                not_equal: '&#8800;',
                less: '<',
                less_or_equal: '&#8804;',
                greater: '>',
                greater_or_equal: '&#8805;',
                between: '<?php echo TS_('between'); ?>',
                not_between: '<?php echo TS_('not between'); ?>',
                contains: '<?php echo TS_('contains'); ?>',
                not_contains: '<?php echo TS_('doesn\'t contain'); ?>',
                blank: ' ',
                user_tagged: '<?php echo TS_('user is tagged with all of'); ?>',
                user_not_tagged: '<?php echo TS_('user is not tagged with any of'); ?>',
            }
        },
        templates: {
            group: '\
<dl id="{{= it.group_id }}" class="rules-group-container"> \
  <dt class="rules-group-header"> \
    <div class="btn-group pull-right group-actions"> \
      {{? it.level>1 }} \
        <button type="button" class="btn btn-xs btn-default" data-delete="group"> \
          <i class="{{= it.icons.remove_group }}"></i> {{= it.translate("delete_group") }} \
        </button> \
      {{?}} \
    </div> \
    <div class="btn-group group-conditions"> \
      {{~ it.conditions: condition }} \
        <label class="btn btn-xs btn-default"> \
          <input type="radio" name="{{= it.group_id }}_cond" value="{{= condition }}"> {{= it.translate("conditions", condition) }} \
        </label> \
      {{~}} \
    </div> \
    {{? it.settings.display_errors }} \
      <div class="error-container"><i class="{{= it.icons.error }}"></i></div> \
    {{?}} \
  </dt> \
  <dd class=rules-group-body> \
    <ul class=rules-list>\
    <li class="rule-container">\
      <button type="button" class="btn btn-xs btn-default" data-add="rule"> \
        <i class="{{= it.icons.add_rule }}"></i> {{= it.translate("add_rule") }} \
      </button> \
      {{? it.settings.allow_groups===-1 || it.settings.allow_groups>=it.level }} \
        <button type="button" class="btn btn-xs btn-default" data-add="group"> \
          <i class="{{= it.icons.add_group }}"></i> {{= it.translate("add_group") }} \
        </button> \
      {{?}} \
    </li> \
    </ul> \
  </dd> \
</dl>',
            rule: '\
<li id="{{= it.rule_id }}" class="rule-container"> \
  <div class="rule-header"> \
    <div class="btn-group pull-right rule-actions"> \
      <button type="button" class="btn btn-xs btn-default" data-delete="rule"> \
        <i class="{{= it.icons.remove_rule }}"></i> {{= it.translate("delete_rule") }} \
      </button> \
    </div> \
  </div> \
  {{? it.settings.display_errors }} \
    <div class="error-container"><i class="{{= it.icons.error }}"></i></div> \
  {{?}} \
  <div class="rule-filter-container"></div> \
  <div class="rule-operator-container"></div> \
  <div class="rule-value-container"></div> \
</li>',
        },    
        filters: [<?php echo implode( ',', $js_filters ); ?>],
        rules: <?php echo param_format_condition( $filter_query, 'js', array_keys( $filter_fields ) ); ?>,
    } );

    // Prepare form before submitting:
    jQuery( '#evo_results_filters' ).closest( 'form' ).on( 'submit', function()
    {
        // Convert filter fields to JSON format:
        var result = jQuery( '#evo_results_filters' ).queryBuilder( 'getRules' );
        if( result === null )
        {    // Stop submitting on wrong SQL:
            return false;
        }
        else
        {    // Set query rules to hidden field before submitting:
            jQuery( 'input[name=filter_query]' ).val( JSON.stringify( result ) );
        }
    } );

    // Fix space of blank hidden operator:
    evo_fix_query_builder_blank_operator();
    jQuery( '#evo_results_filters' ).on( 'afterUpdateRuleFilter.queryBuilder.filter', function()
    {
        evo_fix_query_builder_blank_operator();
    } );
    function evo_fix_query_builder_blank_operator()
    {
        jQuery( '.rule-container .rule-operator-container' ).each( function()
        {
            if( jQuery( this ).find( 'option' ).length == jQuery( this ).find( 'option[value=blank]' ).length )
            {    // Hide container if rule uses only single blank operator:
                jQuery( this ).hide();
            }
            else
            {    // Show container with other operators:
                jQuery( this ).show();
            }
        } );
    }
} );
</script>
<?php
   
}


   
/**
     * Display the column selection
     */
   
function display_colselect()
    {
        if( empty(
$this->colselect_area ) )
        {    
// We don't want to display a col selection section:
           
return;
        }

        if( empty(
$this->param_prefix ) )
        {    
// Deny to use a list without prefix
           
debug_die( 'You must define a $param_prefix before you can use columns select area.' );
        }

       
$option_name = $this->param_prefix.'colselect';

       
// "Columns" with JS toggle to reveal form:
       
echo '<span onclick="toggle_filter_area(\''.$option_name.'\')"'
           
.' class="btn btn-xs btn-info">'
           
.get_icon( 'filters_show', 'imgtag', array( 'id' => 'clickimg_'.$option_name ) )
            .
' '.T_('Columns')
        .
'</span>';

       
// Begining of the div with columns:
       
echo '<div id="clickdiv_'.$option_name.'" style="display:none">';

        echo
'</div>';
    }


   
/**
     * Display the filtering form
     */
   
function display_filters()
    {
        global
$debug, $Session;

        if( empty(
$this->filter_area ) )
        {    
// We don't want to display a filters section:
           
return;
        }

        if( empty(
$this->param_prefix ) )
        {    
// Deny to use a list without prefix
           
debug_die( 'You must define a $param_prefix before you can use filters.' );
        }

       
$custom_option_name = $this->param_prefix.'custom_filters';
       
$advanced_option_name = $this->param_prefix.'advanced_filters';
       
$preset_name = $this->param_prefix.'filter_preset';
       
$submit_title = ( empty( $this->filter_area['submit_title'] ) ? T_('Apply filters') : $this->filter_area['submit_title'] );

       
// Do we already have a form?
       
$create_new_form = ! isset( $this->Form );

        echo
$this->replace_vars( $this->params['filters_start'] );

       
$this->current_filter_preset = param( $preset_name, 'string', NULL );
        if(
$this->current_filter_preset !== NULL )
        {    
// Store new preset in Session:
           
$Session->set( $preset_name, $this->current_filter_preset );
           
$Session->dbsave();
        }
        if(
$this->current_filter_preset === NULL )
        {    
// Try to get preset from Session:
           
$this->current_filter_preset = $Session->get( $preset_name );
        }
        if(
$this->current_filter_preset === NULL )
        {    
// Use 'all' preset filter by default:
           
$this->current_filter_preset = 'all';
        }

       
$custom_fold_state = ( $this->current_filter_preset == 'custom' ? 'expanded' : 'collapsed' );
       
$advanced_fold_state = ( $this->current_filter_preset == 'advanced' ? 'expanded' : 'collapsed' );

       
//____________________________________ Filter presets ____________________________________

       
echo '<span class="btn-group">';

       
// Display all presers;
       
if( ! empty( $this->filter_area['presets'] ) )
        {    
// Display preset filters:
           
           
foreach( $this->filter_area['presets'] as $key => $preset )
            {
               
// Link for preset filter:
               
echo '<a href="'.$preset[1].'" class="btn btn-xs btn-info'.( $this->current_filter_preset == $key ? ' active' : '' ).'">'.$preset[0].'</a>';
            }
        }

        if( ! empty(
$this->filter_area['callback'] ) )
        {    
// "Custom preset" with JS toggle to reveal form:
           
echo '<span onclick="toggle_filter_area(\''.$custom_option_name.'\')"'
                   
.' class="btn btn-xs btn-info'.( $this->current_filter_preset == 'custom' ? ' active' : '' ).'">'
                   
.get_icon( ( $custom_fold_state == 'collapsed' ? 'filters_show' : 'filters_hide' ), 'imgtag', array( 'id' => 'clickimg_'.$custom_option_name ) )
                    .
' '.T_('Custom filters')
                .
'</span>';
        }

        if(
is_admin_page() && ! empty( $this->filter_area['callback_advanced'] ) )
        {    
// "Advanced preset" with JS toggle to reveal form ONLY on back-office:
           
echo '<span onclick="toggle_filter_area(\''.$advanced_option_name.'\')"'
                   
.' class="btn btn-xs btn-info'.( $this->current_filter_preset == 'advanced' ? ' active' : '' ).'">'
                   
.get_icon( ( $advanced_fold_state == 'collapsed' ? 'filters_show' : 'filters_hide' ), 'imgtag', array( 'id' => 'clickimg_'.$advanced_option_name ) )
                    .
' '.T_('Advanced filters')
                .
'</span>';
        }

        echo
'</span>'; // End of <span class="btn-group">

        //_________________________________________________________________________________________

       
if( $debug > 1 )
        {
            echo
' <span class="notes">('.$custom_option_name.':'.$custom_fold_state.'; '.$advanced_option_name.':'.$custom_fold_state.')</span>';
            echo
' <span id="asyncResponse"></span>';
        }

       
// ---------------- CUSTOM FILTERS ---------------- :
       
if( ! empty( $this->filter_area['callback'] ) )
        {    
// Display Custom Filters Form fields:

            // Begining of the Custom filters area:
           
echo '<div id="clickdiv_'.$custom_option_name.'"'.( $custom_fold_state == 'collapsed' ? ' style="display:none"' : '' ).'>';

            if(
$create_new_form )
            {    
// We do not already have a form surrounding the whole results list:
               
$ignore = ( empty( $this->filter_area['url_ignore'] ) ? $this->page_param : $this->filter_area['url_ignore'] );

               
$this->Form = new Form( regenerate_url( $ignore, '', '', '&' ), $this->param_prefix.'form_search', 'get', 'blockspan' );

               
$this->Form->begin_form( '' );
            }

            if( ! isset(
$this->filter_area['apply_filters_button'] ) || $this->filter_area['apply_filters_button'] != 'none' )
            {    
// Display a filter button only when it is not hidden by param:  (Hidden example: BackOffice > Contents > Posts)
               
echo $this->params['filter_button_before'];
               
$submit_name = empty( $this->filter_area['submit'] ) ? 'colselect_submit' : $this->filter_area['submit'];
               
$this->Form->button_input( array(
                           
'tag'   => 'button',
                           
'name'  => $submit_name,
                           
'value' => get_icon( 'filter' ).' '.$submit_title,
                           
'class' => $this->params['filter_button_class']
                    ) );
                echo
$this->params['filter_button_after'];
            }

            if( ! empty(
$this->force_checkboxes_to_inline ) )
            {
// Set this to TRUE in order to display all checkboxes before labels
               
$this->Form->force_checkboxes_to_inline = true;
            }

           
// Call function to display custom filters:
           
$func = $this->filter_area['callback'];
           
$func( $this->Form );

           
// Use reserved preset name for filtering by submitted form:
           
$this->Form->hidden( $this->param_prefix.'filter_preset', 'custom' );
            if(
method_exists( $this, 'save_filterset' ) )
            {    
// For table that can save filterset we should set new filter on each form submit:
               
$this->Form->hidden( 'filter', 'new' );
            }

            if(
$create_new_form )
            {
// We do not already have a form surrounding the whole result list:
               
$this->Form->end_form( '' );
                unset(
$this->Form );    // forget about this temporary form
           
}

            echo
'</div>'; // End of the Custom filters area.
       
}

       
// ---------------- ADVANCED FILTERS ---------------- :
       
if( is_admin_page() && ! empty($this->filter_area['callback_advanced'] ) )
        {    
// Display Advanced Filters Form fields ONLY on back-office:

            // Begining of the Advanced filters area:
           
echo '<div id="clickdiv_'.$advanced_option_name.'" class="evo_results_filters__advanced"'.( $advanced_fold_state == 'collapsed' ? ' style="display:none"' : '' ).'>';

            if(
$create_new_form )
            {    
// We do not already have a form surrounding the whole results list:
               
$ignore = ( empty( $this->filter_area['url_ignore'] ) ? $this->page_param : $this->filter_area['url_ignore'] );

               
$this->Form = new Form( regenerate_url( $ignore, '', '', '&' ), $this->param_prefix.'form_search', 'get', 'blockspan' );

               
$this->Form->begin_form( '' );
            }

           
// Print out Advanced filters which use JavaScript plugin QueryBuilder:
           
$this->display_advanced_filter_fields( $this->Form );

           
// Use reserved preset name for filtering by submitted form:
           
$this->Form->hidden( $this->param_prefix.'filter_preset', 'advanced' );
            if(
method_exists( $this, 'save_filterset' ) )
            {    
// For table that can save filterset we should set new filter on each form submit:
               
$this->Form->hidden( 'filter', 'new' );
            }

            if( ! isset(
$this->filter_area['apply_filters_button'] ) || $this->filter_area['apply_filters_button'] != 'none' )
            {    
// Display a filter button only when it is not hidden by param:  (Hidden example: BackOffice > Contents > Posts)
               
echo $this->params['bottom_filter_button_before'];
               
$submit_name = empty( $this->filter_area['submit'] ) ? 'colselect_submit' : $this->filter_area['submit'];
               
$this->Form->button_input( array(
                       
'tag'   => 'button',
                       
'name'  => $submit_name,
                       
'value' => get_icon( 'filter' ).' '.$submit_title,
                       
'class' => $this->params['bottom_filter_button_class']
                    ) );
                echo
$this->params['bottom_filter_button_after'];
            }

            if(
$create_new_form )
            {
// We do not already have a form surrounding the whole result list:
               
$this->Form->end_form( '' );
                unset(
$this->Form );    // forget about this temporary form
           
}

            if( isset(
$this->filter_area['advanced_defaults_jsfunc'] ) )
            {    
// Display a button to change default advanced filters:
               
echo '<button class="btn btn-xs btn-default evo_results_filters__defaults_btn" onclick="'.$this->filter_area['advanced_defaults_jsfunc'].'"><span class="fa fa-cog pointer"></span> '.T_('Defaults').'</button>';
            }

            echo
'</div>'; // End of the Advanced filters area.
       
}

        echo
$this->params['filters_end'];
    }


   
/**
     * Display list/table start.
     *
     * Typically outputs UL or TABLE tags.
     */
   
function display_list_start()
    {
        if(
$this->total_pages == 0 )
        {
// There are no results! Nothing to display!
           
if( isset( $this->filter_area['is_filtered'] ) && ! $this->filter_area['is_filtered'] )
            {    
// If this table list is filtered and no results then we should collapse the filter area because nothing to filter:
               
echo '<script type="text/javascript">toggle_filter_area( "'.$this->param_prefix.'filters", "collapse" )</script>';
            }
            echo
$this->replace_vars( $this->params['no_results_start'] );
        }
        else
        {
// We have rows to display:
           
if( ! empty( $this->list_mass_actions ) )
            {    
// Start form for list with mass actions:
               
$this->Form = new Form();
               
$this->Form->begin_form();
                if( ! empty(
$this->list_form_hiddens ) )
                {
                    foreach(
$this->list_form_hiddens as $list_form_hidden_key => $list_form_hidden_value )
                    {
                        if(
$list_form_hidden_key == 'crumb' )
                        {    
// Special hidden for crumb:
                           
$this->Form->add_crumb( $list_form_hidden_value );
                        }
                        else
                        {    
// Normal hidden field:
                           
$this->Form->hidden( $list_form_hidden_key, $list_form_hidden_value );
                        }
                    }
                }
            }

           
$list_class = empty( $this->params['list_class'] ) ? '' : ' '.$this->params['list_class'];
           
$list_attrs = empty( $this->params['list_attrib'] ) ? '' : ' '.$this->params['list_attrib'];
            echo
str_replace( array( ' $list_class$', ' $list_attrib$' ), array( $list_class, $list_attrs ), $this->params['list_start'] );
        }
    }


   
/**
     * Display list/table end.
     *
     * Typically outputs </ul> or </table>
     */
   
function display_list_end()
    {
        if(
$this->total_pages == 0 )
        {
// There are no results! Nothing to display!
           
echo $this->replace_vars( $this->params['no_results_end'] );
        }
        else
        {    
// We have rows to display:
           
echo $this->params['list_end'];

            if( ! empty(
$this->list_mass_actions ) && isset( $this->Form ) )
            {    
// Start form for list with mass actions:
               
$this->Form->end_form();
            }
        }
    }


   
/**
     * Display list/table head.
     *
     * This includes list head/title and filters.
     * EXPERIMENTAL: also dispays <tfoot>
     */
   
function display_head()
    {
        if(
is_ajax_content() )
        {    
// Don't display this content on AJAX request
           
return;
        }

       
// DISPLAY TITLE:
       
if( isset($this->title) )
        {
// A title has been defined for this result set:
           
echo $this->replace_vars( $this->params['head_title'] );
        }

       
// DISPLAY FILTERS:
       
$this->display_filters();

       
// DISPLAY COL SELECTION
       
$this->display_colselect();


       
// Experimental:
        /*echo $this->params['tfoot_start'];
        echo $this->params['tfoot_end'];*/
   
}



   
/**
     * Display column headers
     */
   
function display_col_headers()
    {
        echo
$this->params['head_start'];

        if( isset(
$this->cols ) )
        {

            if( !isset(
$this->nb_cols) )
            {    
// Needed for sort strings:
               
$this->nb_cols = count($this->cols);
            }


           
$th_group_activated = false;

           
// Loop on all columns to see if we have th_group columns:
           
foreach( $this->cols as $col )
            {
                if( isset(
$col['th_group'] )    )
                {    
// We have a th_group column, so break:
                   
$th_group_activated = true;
                    break;
                }
            }

           
$current_th_group_colspan = 1;
           
$current_th_colspan = 1;
           
$current_th_group_title = NULL;
           
$current_th_title = NULL;
           
$header_cells = array();

           
// Loop on all columns to get an array of header cells description
            // Each header cell will have a colspan and rowspan value
            // The line 0 is reserved for th_group
            // The line 1 is reserved for th
           
foreach( $this->cols as $key=>$col )
            {
               
//_______________________________ TH GROUP __________________________________

               
if( isset( $col['th_group'] ) )
                {    
// The column has a th_group
                   
if( is_null( $current_th_group_title ) || $col['th_group'] != $current_th_group_title )
                    {    
// It's the begining of a th_group colspan (line0):

                        //Initialize current th_group colspan to 1 (line0):
                       
$current_th_group_colspan = 1;

                       
// Set colspan and rowspan colum for line0 to 1:
                       
$header_cells[0][$key]['colspan'] = 1;
                       
$header_cells[0][$key]['rowspan'] = 1;
                    }
                    else
                    {    
// The column is part of a th group colspan
                        // Update the first th group colspan cell
                       
$header_cells[0][$key-$current_th_group_colspan]['colspan']++;

                       
// Set the colspan column to 0 to not display it
                       
$header_cells[0][$key]['colspan'] = 0;
                       
$header_cells[0][$key]['rowspan'] = 0;

                       
//Update current th_group colspan to 1 (line0):
                       
$current_th_group_colspan++;
                    }

                   
// Update current th group title:
                   
$current_th_group_title =     $col['th_group'];
                }

               
//___________________________________ TH ___________________________________

               
if( is_null( $current_th_title ) || $col['th'] != $current_th_title )
                {    
// It's the begining of a th colspan (line1)

                    //Initialize current th colspan to 1 (line1):
                   
$current_th_colspan = 1;

                   
// Update current th title:
                   
$current_th_title = $col['th'];

                    if(
$th_group_activated  && !isset( $col['th_group'] ) )
                    {
// We have to lines and the column has no th_group, so it will be a "rowspan2"

                        // Set the cell colspan and rowspan values for the line0:
                       
$header_cells[0][$key]['colspan'] = 1;
                       
$header_cells[0][$key]['rowspan'] = 2;

                       
// Set the cell colspan and rowspan values for the line1, to do not display it:
                       
$header_cells[1][$key]['colspan'] = 0;
                       
$header_cells[1][$key]['rowspan'] = 0;
                    }
                    else
                    {    
// The cell has no rowspan
                       
$header_cells[1][$key]['colspan'] = 1;
                       
$header_cells[1][$key]['rowspan'] = 1;
                    }
                }
                else
                {    
// The column is part of a th colspan
                   
if( $th_group_activated && !isset( $col['th_group'] ) )
                    {    
// We have to lines and the column has no th_group, the colspan is "a rowspan 2"

                        // Update the first th cell colspan in line0
                       
$header_cells[0][$key-$current_th_colspan]['colspan']++;

                       
// Set the cell colspan to 0 in line0 to not display it:
                       
$header_cells[0][$key]['colspan'] = 0;
                       
$header_cells[0][$key]['rowspan'] = 0;
                    }
                    else
                    {
// Update the first th colspan cell in line1
                       
$header_cells[1][$key-$current_th_colspan]['colspan']++;
                    }

                   
// Set the cell colspan to 0 in line1 to do not display it:
                   
$header_cells[1][$key]['colspan'] = 0;
                   
$header_cells[1][$key]['rowspan'] = 0;

                   
$current_th_colspan++;
                }
            }

           
// ________________________________________________________________________________

           
if( !$th_group_activated )
            {    
// We have only the "th" line to display
               
$start = 1;
            }
            else
            {    
// We have the "th_group" and the "th" lines to display
               
$start = 0;
            }

           
//__________________________________________________________________________________

            // Loop on all headers lines:
           
for( $i = $start; $i <2 ; $i++ )
            {
                echo
$this->params['line_start_head'];
               
// Loop on all headers lines cells to display them:
               
foreach( $header_cells[$i] as $key=>$cell )
                {
                    if(
$cell['colspan'] )
                    {    
// We have to dispaly cell:
                       
if( $i == 0 && $cell['rowspan'] != 2 )
                        {    
// The cell is a th_group
                           
$th_title = $this->cols[$key]['th_group'];
                           
$col_order = isset( $this->cols[$key]['order_group'] );
                        }
                        else
                        {    
// The cell is a th
                           
$th_title = $this->cols[$key]['th'];
                           
$col_order = isset( $this->cols[$key]['order'] )
                            || isset(
$this->cols[$key]['order_objects_callback'] )
                            || isset(
$this->cols[$key]['order_rows_callback'] );
                        }


                        if( isset(
$this->cols[$key]['th_class'] ) )
                        {    
// We have a class for the th column
                           
$class = $this->cols[$key]['th_class'];
                        }
                        else
                        {    
// We have no class for the th column
                           
$class = '';
                        }

                        if(
$key == 0 && isset($this->params['colhead_start_first']) )
                        {
// Display first column start:
                           
$output = $this->params['colhead_start_first'];

                           
// Add the total column class in the grp col start first param class:
                           
$output = str_replace( '$class$', $class, $output );
                        }
                        elseif( (
$key + $cell['colspan'] ) == (count( $this->cols) ) && isset($this->params['colhead_start_last']) )
                        {
// Last column can get special formatting:
                           
$output = $this->params['colhead_start_last'];

                           
// Add the total column class in the grp col start end param class:
                           
$output = str_replace( '$class$', $class, $output );
                        }
                        else
                        {
// Display regular colmun start:
                           
$output = $this->params['colhead_start'];

                           
// Replace the "class_attrib" in the grp col start param by the td column class
                           
$output = str_replace( '$class_attrib$', 'class="'.$class.'"', $output );
                        }

                       
// Replace column header title attribute
                       
if( isset( $this->cols[$key]['th_title'] ) )
                        {
// Column header title is set
                           
$output = str_replace( '$title_attrib$', ' title="'.$this->cols[$key]['th_title'].'"', $output );
                        }
                        else
                        {
// Column header title is not set, replace with empty string
                           
$output = str_replace( '$title_attrib$', '', $output );
                        }

                       
// Set colspan and rowspan values for the cell:
                       
$output = preg_replace( '#(<)([^>]*)>$#', '$1$2 colspan="'.$cell['colspan'].'" rowspan="'.$cell['rowspan'].'">' , $output );

                        echo
$output;

                        if(
$col_order )
                        {
// The column can be ordered:
                           
$col_sort_values = $this->get_col_sort_values( $key );


                           
// Determine CLASS SUFFIX depending on wether the current column is currently sorted or not:
                           
if( !empty($col_sort_values['current_order']) )
                            {
// We are currently sorting on the current column:
                               
$class_suffix = '_current';
                            }
                            else
                            {    
// We are not sorting on the current column:
                               
$class_suffix = '_sort_link';
                            }

                           
// Display title depending on sort type/mode:
                           
if( $this->params['sort_type'] == 'single' )
                            {
// single column sort type:

                                // Title with toggle:
                               
echo '<a href="'.$col_sort_values['order_toggle'].'"'
                                           
.' title="'.T_('Change Order').'"'
                                           
.' class="single'.$class_suffix.'"'
                                           
.'>'.$th_title.'</a>';

                               
// Icon for ascending sort:
                               
echo '<a href="'.$col_sort_values['order_asc'].'"'
                                           
.' title="'.T_('Ascending order').'"'
                                           
.'>'.$this->params['sort_asc_'.($col_sort_values['current_order'] == 'ASC' ? 'on' : 'off')].'</a>';

                               
// Icon for descending sort:
                               
echo '<a href="'.$col_sort_values['order_desc'].'"'
                                           
.' title="'.T_('Descending order').'"'
                                           
.'>'.$this->params['sort_desc_'.($col_sort_values['current_order'] == 'DESC' ? 'on' : 'off')].'</a>';

                            }
                            else
                            {
// basic sort type (toggle single column):

                               
if( $col_sort_values['current_order'] == 'ASC' )
                                {
// the sorting is ascending and made on the current column
                                   
$sort_icon = $this->params['basic_sort_asc'];
                                }
                                elseif(
$col_sort_values['current_order'] == 'DESC' )
                                {
// the sorting is descending and made on the current column
                                   
$sort_icon = $this->params['basic_sort_desc'];
                                }
                                else
                                {
// the sorting is not made on the current column
                                   
$sort_icon = $this->params['basic_sort_off'];
                                }

                               
// Toggle Icon + Title
                                // Set link title only if the column header title was not set
                               
$link_title = isset( $this->cols[$key]['th_title'] ) ? '' : ' title="'.T_('Change Order').'"';
                                echo
'<a href="'.$col_sort_values['order_toggle'].'"'
                                           
.$link_title
                                           
.' class="basic'.$class_suffix.'"'
                                           
.'>'.$sort_icon.' '.$th_title.'</a>';

                            }

                        }
                        elseif(
$th_title )
                        {
// the column can't be ordered, but we still have a header defined:
                           
echo '<span>'.$th_title.'</span>';
                        }
                       
// </td>
                       
echo $this->params['colhead_end'];
                    }
                }
               
// </tr>
               
echo $this->params['line_end'];
            }
        }
// this->cols not set

       
echo $this->params['head_end'];
    }


   
/**
     *
     */
   
function display_body_start()
    {
        echo
$this->params['body_start'];

       
$this->displayed_lines_count = 0;

    }


   
/**
     *
     */
   
function display_body_end()
    {
        echo
$this->params['body_end'];
    }


   
/**
     *
     */
   
function display_line_start( $is_last = false, $is_fadeout_line = false )
    {
        if(
$this->displayed_lines_count % 2 )
        {
// Odd line:
           
if( $is_last )
               
$start_tag = $this->params['line_start_odd_last'];
            else
               
$start_tag = $this->params['line_start_odd'];
        }
        else
        {
// Even line:
           
if( $is_last )
               
$start_tag = $this->params['line_start_last'];
            else
               
$start_tag = $this->params['line_start'];
        }

        if(
$is_fadeout_line )
        {    
// Add css class for this row to highlight:
           
$start_tag = update_html_tag_attribs( $start_tag, array( 'class' => 'evo_highlight' ) );
        }

        echo
$start_tag;

       
$this->displayed_cols_count = 0;
    }


   
/**
     *
     */
   
function display_line_end()
    {
        echo
$this->params['line_end'];

       
$this->displayed_lines_count ++;
    }


   
/**
     * Start a column (data).
     *
     * @param array Additional attributes for the <td> tag (attr_name => attr_value).
     * @param object|NULL Current row data (The var $row is requested inside of $this->parse_col_content() )
     */
   
function display_col_start( $extra_attr = array(), $row = NULL )
    {
       
// Get colum definitions for current column:
       
$col = $this->cols[$this->displayed_cols_count];

        if( isset(
$col['td_class'] ) )
        {    
// We have a class for the total column
           
$class = $col['td_class'];
        }
        else
        {    
// We have no class for the total column
           
$class = '';
        }

        if( (
$this->displayed_cols_count == 0) && isset($this->params['col_start_first']) )
        {
// Display first column column start:
           
$output = $this->params['col_start_first'];
           
// Add the total column class in the col start first param class:
           
$output = str_replace( '$class$', $class, $output );
        }
        elseif( (
$this->displayed_cols_count == count($this->cols)-1) && isset($this->params['col_start_last']) )
        {
// Last column can get special formatting:
           
$output = $this->params['col_start_last'];
           
// Add the total column class in the col start end param class:
           
$output = str_replace( '$class$', $class, $output );
        }
        else
        {
// Display regular colmun start:
           
$output = $this->params['col_start'];
           
// Replace the "class_attrib" in the total col start param by the td column class
           
$output = str_replace( '$class_attrib$', 'class="'.$class.'"', $output );
        }

        if( isset(
$col['td_colspan'] ) )
        {    
// Initialize colspan attribute:
           
if( method_exists( $this, 'parse_col_content' ) )
            {
               
$colspan = $this->parse_col_content( $col['td_colspan'] );
               
$colspan = eval( "return '$colspan';" );
            }
            else
            {
               
$colspan = $col['td_colspan'];
            }
            if(
$colspan < 0 )
            {    
// We want to substract columns from the total count:
               
$colspan = $this->nb_cols + $colspan;
            }
            elseif(
$colspan == 0 )
            {    
// Use a count of columns:
               
$colspan = $this->nb_cols;
            }
           
$colspan = intval( $colspan );
            if(
$colspan === 1 )
            {    
// Don't create attribute "colspan" with value "1":
               
$output = str_replace( '$colspan_attrib$', '', $output );
            }
            else
            {
               
$output = str_replace( '$colspan_attrib$', 'colspan="'.$colspan.'"', $output );
               
// Store current colspan value in order to skip next columns:
               
$this->current_colspan = $colspan;
            }
        }
        else
        {    
// Remove non-HTML attrib:
           
$output = str_replace( '$colspan_attrib$', '', $output );
        }

       
// Custom attributes:
        // Tblue> TODO: Make this more elegant (e. g.: replace "$extra_attr$" with the attributes string).
       
if( $extra_attr )
        {
            if ( ! isset (
$extra_attr['format_to_output']))
            {
               
$output = substr( $output, 0, -1 ).get_field_attribs_as_string( $extra_attr ).'>';
            }
            else
            {
               
$format_to_output = $extra_attr['format_to_output'];
                unset(
$extra_attr['format_to_output']);
               
$output = substr( $output, 0, -1 ).get_field_attribs_as_string( $extra_attr, $format_to_output ).'>';


            }

        }
       
// Check variables in column declaration:
       
$output = $this->parse_class_content( $output );
        echo
$output;
    }


 
/**
     *
     */
   
function display_col_end()
    {
        echo
$this->params['col_end'];

       
$this->displayed_cols_count ++;
    }


   
/**
     * Widget callback for template vars.
     *
     * This allows to replace template vars, see {@link Widget::replace_callback()}.
     *
     * @return string
     */
   
function replace_callback( $matches )
    {
       
// echo '['.$matches[1].']';
       
switch( $matches[1] )
        {
            case
'reset_filters_button':
               
// Resetting the filters is the same as applying preset 'all' (should be defined for all)
               
if( !isset($this->filter_area['presets']['all'] ) )
                {    
// Preset "all" not defined, we don't know how to reset.
                   
return '';
                }
                if( empty(
$this->current_filter_preset) || $this->current_filter_preset == 'all' )
                {
// No filters applied:
                   
return '';
                }
                return
'<a href="'.$this->filter_area['presets']['all'][1].'" class="btn btn-sm btn-warning">'.get_icon('reset_filters').T_('Remove filters').'</a>';

            case
'nb_cols' :
               
// Number of columns in result:
               
if( !isset($this->nb_cols) )
                {
                   
$this->nb_cols = count($this->cols);
                }
                return
$this->nb_cols;

            default :
                return
parent::replace_callback( $matches );
        }
    }

   
/**
     * Handle variable subtitutions for class column contents.
     *
     * This is one of the key functions to look at when you want to use the Results class.
     * - #var#
     */
   
function parse_class_content( $content )
    {
       
// Make variable substitution for RAWS:
       
while (preg_match('!\# (\w+) \#!ix', $content, $matchesarray))
        {
// Replace all matches to the content of the current row's cell. That means that several variables can be inserted to the class.
           
if (! empty($this->rows[$this->current_idx]->{$matchesarray[1]}))
            {
               
$content = str_replace($matchesarray[0],$this->rows[$this->current_idx]->{$matchesarray[1]} , $content);
            }
            else
            {
               
$content = str_replace($matchesarray[0], 'NULL' , $content);
            }
        }

        while (
preg_match('#% (.+?) %#ix', $content, $matchesarray))
        {
             eval(
'$result = '.$matchesarray[1].';');
             
$content = str_replace($matchesarray[0],$result, $content);
        }

        return
$content;
    }


   
/**
     * Init results params from skin template params. It's used when Results table is filled from ajax result.
     *
     * @param string the template param which can have values( 'admin', 'front' )
     * @param string the name of the skin
     */
   
function init_params_by_skin( $skin_type, $skin_name )
    {
        switch(
$skin_type )
        {
            case
'admin': // admin skin type
               
global $adminskins_path;
                require_once
$adminskins_path.$skin_name.'/_adminUI.class.php';
               
$this->params = AdminUI::get_template( 'Results' );
                break;

            case
'front': // front office skin type
               
global $skins_path;
                require_once
$skins_path.$skin_name.'/_skin.class.php';
               
$this->params = Skin::get_template( 'Results' );
                break;

            default:
               
debug_die( 'Invalid results template param!' );
        }
    }

}

?>