<?php
/**
* TablePress Rendering Class
*
* @package TablePress
* @subpackage Rendering
* @author Tobias Bäthge
* @since 1.0.0
*/
// Prohibit direct script loading
defined( 'ABSPATH' ) || die( 'No direct script access allowed!' );
/**
* TablePress Rendering Class
* @package TablePress
* @subpackage Rendering
* @author Tobias Bäthge
* @since 1.0.0
*/
class TablePress_Render {
/**
* Table data that is rendered
*
* @since 1.0.0
*
* @var array
*/
protected $table;
/**
* Table options that influence the output result
*
* @since 1.0.0
*
* @var array
*/
protected $render_options = array();
/**
* Rendered HTML of the table
*
* @since 1.0.0
*
* @var string
*/
protected $output;
/**
* Instance of EvalMath class
*
* @since 1.0.0
*
* @var object
*/
protected $evalmath;
/**
* Trigger words for colspan, rowspan, or the combination of both
*
* @since 1.0.0
*
* @var array
*/
protected $span_trigger = array(
'colspan' => '#colspan#',
'rowspan' => '#rowspan#',
'span' => '#span#'
);
/**
* Buffer to store the counts of rowspan per column, initialized in _render_table()
*
* @since 1.0.0
*
* @var array
*/
protected $rowspan = array();
/**
* Buffer to store the counts of colspan per row, initialized in _render_table()
*
* @since 1.0.0
*
* @var array
*/
protected $colspan = array();
/**
* Index of the last row of the visible data in the table, set in _render_table()
*
* @since 1.0.0
*
* @var int
*/
protected $last_row_idx;
/**
* Index of the last column of the visible data in the table, set in _render_table()
*
* @since 1.0.0
*
* @var int
*/
protected $last_column_idx;
/**
* Initialize the Rendering class, include the EvalMath class
*
* @since 1.0.0
*/
public function __construct() {
$this->evalmath = TablePress::load_class( 'EvalMath', 'evalmath.class.php', 'libraries', true ); // true for some default constants
$this->evalmath->suppress_errors = true; // don't raise PHP warnings
}
/**
* Set the table (data, options, visibility, ...) that is to be rendered
*
* @since 1.0.0
*
* @param array $table Table to be rendered
* @param array $render_options Options for rendering, from both "Edit" screen and Shortcode
*/
public function set_input( $table, $render_options ) {
$this->table = $table;
$this->render_options = $render_options;
$this->table = apply_filters( 'tablepress_table_raw_render_data', $this->table, $this->render_options );
}
/**
* Process the table rendering and return the HTML output
*
* @since 1.0.0
*
* @return string HTML of the rendered table (or an error message)
*/
public function get_output() {
// evaluate math expressions/formulas
$this->_evaluate_table_data();
// remove hidden rows and columns
$this->_prepare_render_data();
// generate HTML output
$this->_render_table();
return $this->output;
}
/**
* Remove all cells from the data set that shall not be rendered, because they are hidden
*
* @since 1.0.0
*/
protected function _prepare_render_data() {
$orig_table = $this->table;
$num_rows = count( $this->table['data'] );
$num_columns = ( $num_rows > 0 ) ? count( $this->table['data'][0] ) : 0;
// evaluate show/hide_rows/columns parameters
$actions = array( 'show', 'hide' );
$elements = array( 'rows', 'columns' );
foreach ( $actions as $action ) {
foreach ( $elements as $element ) {
if ( empty( $this->render_options["{$action}_{$element}"] ) ) {
$this->render_options["{$action}_{$element}"] = array();
continue;
}
// add all rows/columns to array if "all" value set for one of the four parameters
if ( 'all' == $this->render_options["{$action}_{$element}"] ) {
$this->render_options["{$action}_{$element}"] = range( 0, ${'num_' . $element} - 1 );
continue;
}
// we have a list of rows/columns (possibly with ranges in it)
$this->render_options["{$action}_{$element}"] = explode( ',', $this->render_options["{$action}_{$element}"] );
// support for ranges like 3-6 or A-BA
$range_cells = array();
foreach ( $this->render_options["{$action}_{$element}"] as $key => $value ) {
$range_dash = strpos( $value, '-' );
if ( false !== $range_dash ) {
unset( $this->render_options["{$action}_{$element}"][$key] );
$start = substr( $value, 0, $range_dash );
if ( ! is_numeric( $start ) )
$start = TablePress::letter_to_number( $start );
$end = substr( $value, $range_dash + 1 );
if ( ! is_numeric( $end ) )
$end = TablePress::letter_to_number( $end );
$current_range = range( $start, $end );
$range_cells = array_merge( $range_cells, $current_range );
}
}
$this->render_options["{$action}_{$element}"] = array_merge( $this->render_options["{$action}_{$element}"], $range_cells );
// parse single letters and
// change from regular numbering to zero-based numbering,
// as rows/columns are indexed from 0 internally, but from 1 externally
foreach ( $this->render_options["{$action}_{$element}"] as $key => $value ) {
if ( ! is_numeric( $value ) )
$value = TablePress::letter_to_number( $value );
$this->render_options["{$action}_{$element}"][$key] = (int)$value - 1;
}
// remove duplicate entries and sort the array
$this->render_options["{$action}_{$element}"] = array_unique( $this->render_options["{$action}_{$element}"] );
sort( $this->render_options["{$action}_{$element}"], SORT_NUMERIC );
}
}
// load information about hidden rows and columns
$hidden_rows = array_keys( $this->table['visibility']['rows'], 0 ); // get indexes of hidden rows (array value of 0))
$hidden_rows = array_merge( $hidden_rows, $this->render_options['hide_rows'] );
$hidden_rows = array_diff( $hidden_rows, $this->render_options['show_rows'] );
$hidden_columns = array_keys( $this->table['visibility']['columns'], 0 ); // get indexes of hidden columns (array value of 0))
$hidden_columns = array_merge( $hidden_columns, $this->render_options['hide_columns'] );
$hidden_columns = array_merge( array_diff( $hidden_columns, $this->render_options['show_columns'] ) );
// remove hidden rows and re-index
foreach ( $hidden_rows as $row_idx ) {
unset( $this->table['data'][$row_idx] );
}
$this->table['data'] = array_merge( $this->table['data'] );
// remove hidden columns and re-index
foreach ( $this->table['data'] as $row_idx => $row ) {
foreach ( $hidden_columns as $col_idx ) {
unset( $row[$col_idx] );
}
$this->table['data'][$row_idx] = array_merge( $row );
}
$this->table = apply_filters( 'tablepress_table_render_data', $this->table, $orig_table, $this->render_options );
}
/**
* Loop through the table to evaluate math expressions/formulas
*
* @since 1.0.0
*/
protected function _evaluate_table_data() {
$orig_table = $this->table;
$rows = count( $this->table['data'] );
$columns = count( $this->table['data'][0] );
for ( $row_idx = 0; $row_idx < $rows; $row_idx++ ) {
for ( $col_idx = 0; $col_idx < $columns; $col_idx++ ) {
$this->table['data'][$row_idx][$col_idx] = $this->_evaluate_cell( $this->table['data'][$row_idx][$col_idx] );
}
}
$this->table = apply_filters( 'tablepress_table_evaluate_data', $this->table, $orig_table, $this->render_options );
}
/**
* Parse and evaluate the content of a cell
*
* @since 1.0.0
*
* @param string $content Content of a cell
* @param array $parents List of cells that depend on this cell (to prevent circle references)
* @return string Result of the parsing/evaluation
*/
protected function _evaluate_cell( $content, $parents = array() ) {
if ( ( '' == $content ) || ( '=' != $content[0] ) )
return $content;
$expression = substr( $content, 1 );
if ( false !== strpos( $expression, '=' ) )
return '!ERROR! Too many "="';
$replaced_references = $replaced_ranges = array();
// remove all whitespace characters
$expression = preg_replace( '#[\r\n\t ]#', '', $expression );
// expand cell ranges (like A3:A6) to a list of single cells (like A3,A4,A5,A6)
if ( preg_match_all( '#([A-Z]+)([0-9]+):([A-Z]+)([0-9]+)#', $expression, $referenced_cell_ranges, PREG_SET_ORDER ) ) {
foreach ( $referenced_cell_ranges as $cell_range ) {
if ( in_array( $cell_range[0], $replaced_ranges, true ) )
continue;
$replaced_ranges[] = $cell_range[0];
if ( isset( $this->known_ranges[ $cell_range[0] ] ) ) {
$expression = preg_replace( '#(?<![A-Z])' . preg_quote( $cell_range[0], '#' ) . '(?![0-9])#', $this->known_ranges[ $cell_range[0] ], $expression );
continue;
}
// no -1 necessary for this transformation, as we don't actually access the table
$first_col = TablePress::letter_to_number( $cell_range[1] );
$first_row = $cell_range[2];
$last_col = TablePress::letter_to_number( $cell_range[3] );
$last_row = $cell_range[4];
$col_start = min( $first_col, $last_col );
$col_end = max( $first_col, $last_col ) + 1; // +1 for loop below
$row_start = min( $first_row, $last_row );
$row_end = max( $first_row, $last_row ) + 1; // +1 for loop below
$cell_list = array();
for ( $col = $col_start; $col < $col_end; $col++ ) {
for ( $row = $row_start; $row < $row_end; $row++ ) {
$column = TablePress::number_to_letter( $col );
$cell_list[] = "{$column}{$row}";
}
}
$cell_list = implode( ',', $cell_list );
$expression = preg_replace( '#(?<![A-Z])' . preg_quote( $cell_range[0], '#' ) . '(?![0-9])#', $cell_list, $expression );
$this->known_ranges[ $cell_range[0] ] = $cell_list;
}
}
// parse and evaluate single cell references (like A3 or XY312), while prohibiting circle references
if ( preg_match_all( '#([A-Z]+)([0-9]+)#', $expression, $referenced_cells, PREG_SET_ORDER ) ) {
foreach ( $referenced_cells as $cell_reference ) {
if ( in_array( $cell_reference[0], $parents, true ) )
return '!ERROR! Circle Reference';
if ( in_array( $cell_reference[0], $replaced_references, true ) )
continue;
$replaced_references[] = $cell_reference[0];
$ref_col = TablePress::letter_to_number( $cell_reference[1] ) - 1;
$ref_row = $cell_reference[2] - 1;
if ( ! isset( $this->table['data'][$ref_row] ) || ! isset( $this->table['data'][$ref_row][$ref_col] ) )
return "!ERROR! Cell {$cell_reference[0]} does not exist";
$ref_parents = $parents;
$ref_parents[] = $cell_reference[0];
$result = $this->table['data'][$ref_row][$ref_col] = $this->_evaluate_cell( $this->table['data'][$ref_row][$ref_col], $ref_parents );
// Bail if there was an error already
if ( false !== strpos( $result, '!ERROR!' ) )
return $result;
// remove all whitespace characters
$result = preg_replace( '#[\r\n\t ]#', '', $result );
// Treat empty cells as 0
if ( '' == $result )
$result = 0;
// Bail if the cell does not result in a number (meaning it was a number or expression before being evaluated)
if ( ! is_numeric( $result ) )
return "!ERROR! {$cell_reference[0]} does not contain a number or expression";
$expression = preg_replace( '#(?<![A-Z])' . preg_quote( $cell_reference[0], '#' ) . '(?![0-9])#', $result, $expression );
}
}
return $this->_evaluate_math_expression( $expression );
}
/**
* Evaluate a math expression
*
* @since 1.0.0
*
* @param string $expression without leading = sign
* @return string Result of the evaluation
*/
protected function _evaluate_math_expression( $expression ) {
// straight up evaluation, without parsing of variable or function assignments (which is why we only need one instance of the object)
$result = $this->evalmath->pfx( $this->evalmath->nfx( $expression ) );
if ( false === $result )
return '!ERROR! ' . $this->evalmath->last_error;
else
return $result;
}
/**
* Generate the HTML output of the table
*
* @since 1.0.0
*/
protected function _render_table() {
$num_rows = count( $this->table['data'] );
$num_columns = ( $num_rows > 0 ) ? count( $this->table['data'][0] ) : 0;
// check if there are rows and columns in the table (might not be the case after removing hidden rows/columns!)
if ( 0 === $num_rows || 0 === $num_columns ) {
$this->output = sprintf( __( '<!-- The table with the ID %s is empty! -->', 'tablepress' ), $this->table['id'] );
return;
}
// counters for spans of rows and columns, init to 1 for each row and column (as that means no span)
$this->rowspan = array_fill( 0, $num_columns, 1 );
$this->colspan = array_fill( 0, $num_rows, 1 );
// allow overwriting the colspan and rowspan trigger keywords, by table ID
$this->span_trigger = apply_filters( 'tablepress_span_trigger_keywords', $this->span_trigger, $this->table['id'] );
// explode from string to array
$this->render_options['column_widths'] = ( ! empty( $this->render_options['column_widths'] ) ) ? explode( '|', $this->render_options['column_widths'] ) : array();
// make array $this->render_options['column_widths'] have $columns entries
$this->render_options['column_widths'] = array_pad( $this->render_options['column_widths'], $num_columns, '' );
$output = '';
if ( $this->render_options['print_name'] ) {
$print_name_html_tag = apply_filters( 'tablepress_print_name_html_tag', 'h2', $this->table['id'] );
$print_name_css_class = apply_filters( 'tablepress_print_name_css_class', "tablepress-table-name tablepress-table-name-id-{$this->table['id']}", $this->table['id'] );
$print_name_html = "<{$print_name_html_tag} class=\"{$print_name_css_class}\">" . $this->safe_output( $this->table['name'] ) . "</{$print_name_html_tag}>\n";
}
if ( $this->render_options['print_description'] ) {
$print_description_html_tag = apply_filters( 'tablepress_print_description_html_tag', 'span', $this->table['id'] );
$print_description_css_class = apply_filters( 'tablepress_print_description_css_class', "tablepress-table-description tablepress-table-description-id-{$this->table['id']}", $this->table['id'] );
$print_description_html = "<{$print_description_html_tag} class=\"{$print_description_css_class}\">" . $this->safe_output( $this->table['description'] ) . "</{$print_description_html_tag}>\n";
}
if ( $this->render_options['print_name'] && 'above' == $this->render_options['print_name_position'] )
$output .= $print_name_html;
if ( $this->render_options['print_description'] && 'above' == $this->render_options['print_description_position'] )
$output .= $print_description_html;
// Deactivate nl2br() for this render process, if "convert_line_breaks" Shortcode parameter is set to false
if ( ! $this->render_options['convert_line_breaks'] )
add_filter( 'tablepress_apply_nl2br', '__return_false', 9 ); // priority 9, so that this filter can easily be overwritten at the default priority
$thead = '';
$tfoot = '';
$tbody = array();
$this->last_row_idx = $num_rows - 1;
$this->last_column_idx = $num_columns - 1;
// loop through rows in reversed order, to search for rowspan trigger keyword
for ( $row_idx = $this->last_row_idx; $row_idx >= 0; $row_idx-- ) {
// last row, need to check for footer (but only if at least two rows)
if ( $this->last_row_idx == $row_idx && $this->render_options['table_foot'] && $num_rows > 1 ) {
$tfoot = $this->_render_row( $row_idx, 'th' );
continue;
}
// first row, need to check for head (but only if at least two rows)
if ( 0 == $row_idx && $this->render_options['table_head'] && $num_rows > 1 ) {
$thead = $this->_render_row( $row_idx, 'th' );
continue;
}
// neither first nor last row (with respective head/foot enabled), so render as body row
$tbody[] = $this->_render_row( $row_idx, 'td' );
}
// Re-instate nl2br() behavior after this render process, if "convert_line_breaks" Shortcode parameter is set to false
if ( ! $this->render_options['convert_line_breaks'] )
remove_filter( 'tablepress_apply_nl2br', '__return_false', 9 ); // priority 9, so that this filter can easily be overwritten at the default priority
// <caption> tag (possibly with "Edit" link)
$caption = apply_filters( 'tablepress_print_caption_text', '', $this->table );
$caption_style = $caption_class = '';
if ( ! empty( $caption ) ) {
$caption_class = apply_filters( 'tablepress_print_caption_class', "tablepress-table-caption tablepress-table-caption-id-{$this->table['id']}", $this->table['id'] );
$caption_class = ' class="' . $caption_class . '"';
}
if ( ! empty( $this->render_options['edit_table_url'] ) ) {
if ( empty( $caption ) )
$caption_style = ' style="caption-side:bottom;text-align:left;border:none;background:none;margin:0;padding:0;"';
else
$caption .= '<br />';
$caption .= "<a href=\"{$this->render_options['edit_table_url']}\" title=\"" . __( 'Edit', 'default' ) . '">' . __( 'Edit', 'default' ) . '</a>';
}
if ( ! empty( $caption ) )
$caption = "<caption{$caption_class}{$caption_style}>{$caption}</caption>\n";
// <colgroup> tag
$colgroup = '';
if ( apply_filters( 'tablepress_print_colgroup_tag', false, $this->table['id'] ) ) {
for ( $col_idx = 0; $col_idx < $columns; $col_idx++ ) {
$attributes = ' class="colgroup-column-' . ( $col_idx + 1 ) . ' "';
$attributes = apply_filters( 'tablepress_colgroup_tag_attributes', $attributes, $this->table['id'], $col_idx + 1 );
$colgroup .= "\t<col{$attributes}/>\n";
}
}
if ( ! empty( $colgroup ) )
$colgroup = "<colgroup>\n{$colgroup}</colgroup>\n";
// <thead>, <tfoot>, and <tbody> tags
if ( ! empty( $thead ) )
$thead = "<thead>\n{$thead}</thead>\n";
if ( ! empty( $tfoot ) )
$tfoot = "<tfoot>\n{$tfoot}</tfoot>\n";
$tbody_class = ( $this->render_options['row_hover'] ) ? ' class="row-hover"' : '';
$tbody = array_reverse( $tbody ); // because we looped through the rows in reverse order
$tbody = "<tbody{$tbody_class}>\n" . implode( '', $tbody ) . "</tbody>\n";
// <table> attributes
$id = ( ! empty( $this->render_options['html_id'] ) ) ? " id=\"{$this->render_options['html_id']}\"" : '';
// classes that will be added to <table class="...">, for CSS styling
$css_classes = array( 'tablepress', "tablepress-id-{$this->table['id']}", $this->render_options['extra_css_classes'] );
$css_classes = apply_filters( 'tablepress_table_css_classes', $css_classes, $this->table['id'] );
$css_classes = explode( ' ', implode( ' ', $css_classes ) ); // $css_classes might contain several classes in one array entry
$css_classes = array_map( 'sanitize_html_class', $css_classes );
$css_classes = array_unique( $css_classes );
$css_classes = trim( implode( ' ', $css_classes ) );
$class = ( ! empty( $css_classes ) ) ? " class=\"{$css_classes}\"" : '';
$summary = apply_filters( 'tablepress_print_summary_attr', '', $this->table );
$summary = ( ! empty( $summary ) ) ? ' summary="' . esc_attr( $summary ) . '"' : '';
$cellspacing = ( false !== $this->render_options['cellspacing'] ) ? ' cellspacing="' . intval( $this->render_options['cellspacing'] ) . '"' : '';
$cellpadding = ( false !== $this->render_options['cellpadding'] ) ? ' cellpadding="' . intval( $this->render_options['cellpadding'] ) . '"' : '';
$border = ( false !== $this->render_options['border'] ) ? ' border="' . intval( $this->render_options['border'] ) . '"' : '';
$output .= "\n<table{$id}{$class}{$summary}{$cellspacing}{$cellpadding}{$border}>\n";
$output .= $caption . $colgroup . $thead . $tfoot . $tbody;
$output .= "</table>\n";
// name/description below table (HTML already generated above)
if ( $this->render_options['print_name'] && 'below' == $this->render_options['print_name_position'] )
$output .= $print_name_html;
if ( $this->render_options['print_description'] && 'below' == $this->render_options['print_description_position'] )
$output .= $print_description_html;
$this->output = apply_filters( 'tablepress_table_output', $output, $this->table, $this->render_options );
}
/**
* Generate the HTML of a row
*
* @since 1.0.0
*
* @param int $row_idx Index of the row to be rendered
* @param string $tag HTML tag to use for the cells (td or th)
* @return string HTML for the row
*/
protected function _render_row( $row_idx, $tag ) {
$row_cells = array();
// loop through cells in reversed order, to search for colspan or rowspan trigger words
for ( $col_idx = $this->last_column_idx; $col_idx >= 0; $col_idx-- ) {
$cell_content = $this->table['data'][ $row_idx ][ $col_idx ];
// print formulas that are escaped with '= (like in Excel) as text:
if ( strlen( $cell_content ) > 2 && "'=" == substr( $cell_content, 0, 2 ) )
$cell_content = substr( $cell_content, 1 );
$cell_content = $this->safe_output( $cell_content );
if ( false !== strpos( $cell_content, '[' ) )
$cell_content = do_shortcode( $cell_content );
$cell_content = apply_filters( 'tablepress_cell_content', $cell_content, $this->table['id'], $row_idx + 1, $col_idx + 1 );
if ( $this->span_trigger['rowspan'] == $cell_content ) { // there will be a rowspan
// check for #rowspan# in first row, which doesn't make sense
if ( ( $row_idx > 1 && $row_idx < $this->last_row_idx )
|| ( 1 == $row_idx && ! $this->render_options['table_head'] ) // no rowspan into table_head
|| ( $this->last_row_idx == $row_idx && ! $this->render_options['table_foot'] ) ) { // no rowspan out of table_foot
$this->rowspan[ $col_idx ]++; // increase counter for rowspan in this column
$this->colspan[ $row_idx ] = 1; // reset counter for colspan in this row, combined col- and rowspan might be happening
continue;
}
// invalid rowspan, so we set cell content from #rowspan# to empty
$cell_content = '';
} elseif ( $this->span_trigger['colspan'] == $cell_content ) { // there will be a colspan
// check for #colspan# in first column, which doesn't make sense
if ( $col_idx > 1
|| ( 1 == $col_idx && ! $this->render_options['first_column_th'] ) ) { // no colspan into first column head
$this->colspan[ $row_idx ]++; // increase counter for colspan in this row
$this->rowspan[ $col_idx ] = 1; // reset counter for rowspan in this column, combined col- and rowspan might be happening
continue;
}
// invalid colspan, so we set cell content from #colspan# to empty
$cell_content = '';
} elseif ( $this->span_trigger['span'] == $cell_content ) { // there will be a combined col- and rowspan
// check for #span# in first column or first or last row, which is not always possible
if ( ( $row_idx > 1 && $row_idx < $this->last_row_idx && $col_idx > 1 )
// we are in first, second, or last row or in the first or second column, so more checks are necessary
|| ( ( 1 == $row_idx && ! $this->render_options['table_head'] ) // no rowspan into table_head
&& ( $col_idx > 1 || ( 1 == $col_idx && ! $this->render_options['first_column_th'] ) ) ) // and no colspan into first column head
|| ( ( $this->last_row_idx == $row_idx && ! $this->render_options['table_foot'] ) // no rowspan out of table_foot
&& ( $col_idx > 1 || ( 1 == $col_idx && ! $this->render_options['first_column_th'] ) ) ) ) // and no colspan into first column head
continue;
// invalid span, so we set cell content from #span# to empty
$cell_content = '';
} elseif ( '' == $cell_content && 0 == $row_idx && $this->render_options['table_head'] ) {
$cell_content = ' '; // make empty cells have a space in the table head, to give sorting arrows the correct position in IE9
}
if ( 0 == $row_idx && $this->render_options['table_head'] )
$cell_content = '<div>' . $cell_content . '</div>';
$span_attr = '';
if ( $this->colspan[ $row_idx ] > 1 ) // we have colspaned cells
$span_attr .= " colspan=\"{$this->colspan[ $row_idx ]}\"";
if ( $this->rowspan[ $col_idx ] > 1 ) // we have rowspaned cells
$span_attr .= " rowspan=\"{$this->rowspan[ $col_idx ]}\"";
$cell_class = 'column-' . ( $col_idx + 1 );
$cell_class = apply_filters( 'tablepress_cell_css_class', $cell_class, $this->table['id'], $cell_content, $row_idx + 1, $col_idx + 1, $this->colspan[ $row_idx ], $this->rowspan[ $col_idx ] );
$class_attr = ( ! empty( $cell_class ) ) ? " class=\"{$cell_class}\"" : '';
$style_attr = ( ( 0 == $row_idx ) && ! empty( $this->render_options['column_widths'][$col_idx] ) ) ? ' style="width:' . preg_replace( '#[^0-9a-z.%]#', '', $this->render_options['column_widths'][$col_idx] ) . ';"' : '';
if ( $this->render_options['first_column_th'] && 0 == $col_idx )
$tag = 'th';
$row_cells[] = "<{$tag}{$span_attr}{$class_attr}{$style_attr}>{$cell_content}</${tag}>";
$this->colspan[ $row_idx ] = 1; // reset
$this->rowspan[ $col_idx ] = 1; // reset
}
$row_class = 'row-' . ( $row_idx + 1 ) ;
if ( $this->render_options['alternating_row_colors'] )
$row_class .= ( 1 == ( $row_idx % 2 ) ) ? ' even' : ' odd';
$row_class = apply_filters( 'tablepress_row_css_class', $row_class, $this->table['id'], $row_cells, $row_idx + 1, $this->table['data'][ $row_idx ] );
if ( ! empty( $row_class ) )
$row_class = " class=\"{$row_class}\"";
$row_cells = array_reverse( $row_cells ); // because we looped through the cells in reverse order
return "<tr{$row_class}>\n\t" . implode( '', $row_cells ) . "\n</tr>\n";
}
/**
* Possibly replace certain HTML entities and replace line breaks with HTML
*
* @TODO: Find a better solution than this function, e.g. something like wpautop()
*
* @since 1.0.0
*
* @param string $string The string to process
* @return string Processed string for output
*/
protected function safe_output( $string ) {
// replace any & with & that is not already an encoded entity (from function htmlentities2 in WP 2.8)
// complete htmlentities2() or htmlspecialchars() would encode <HTML> tags, which we don't want
$string = preg_replace( '/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,4};)/', '&', $string );
// substitute line breaks with HTML <br> tags, nl2br can be overwritten to false, if not wanted
if ( apply_filters( 'tablepress_apply_nl2br', true, $this->table['id'] ) )
$string = nl2br( $string );
return $string;
}
/**
* Get the default render options, null means: Use option from "Edit" screen
*
* @since 1.0.0
*
* @return array Default render options
*/
public function get_default_render_options() {
// Attention: Array keys have to be lowercase, otherwise they won't match the Shortcode attributes, which will be passed in lowercase by WP
return array(
'id' => '',
'column_widths' => '',
'alternating_row_colors' => null,
'row_hover' => null,
'table_head' => null,
'table_foot' => null,
'first_column_th' => false,
'print_name' => null,
'print_name_position' => null,
'print_description' => null,
'print_description_position' => null,
'cache_table_output' => true,
'convert_line_breaks' => true,
'extra_css_classes' => null,
'use_datatables' => null,
'datatables_sort' => null,
'datatables_paginate' => null,
'datatables_paginate_entries' => null,
'datatables_lengthchange' => null,
'datatables_filter' => null,
'datatables_info' => null,
'datatables_scrollx' => null,
'datatables_scrolly' => false,
'datatables_custom_commands' => null,
'datatables_locale' => get_locale(),
'show_rows' => '',
'show_columns' => '',
'hide_rows' => '',
'hide_columns' => '',
'cellspacing' => false,
'cellpadding' => false,
'border' => false
);
}
/**
* Get the CSS code for the Preview iframe
*
* @since 1.0.0
*
* @return string CSS for the Preview iframe
*/
public function get_preview_css() {
if ( is_rtl() ) {
$rtl = "\ndirection: rtl;";
$rtl_align = 'right';
} else {
$rtl = '';
$rtl_align = 'left';
}
return <<<CSS
<style type="text/css">
/* iframe */
body {
font-family: sans-serif;{$rtl}
}
/* inline Shortcodes, in texts */
.table-shortcode-inline {
background: transparent;
border: none;
color: #333333;
width: 100px;
margin: 0;
padding: 0;
font-size: 80%;
-webkit-box-shadow: none;
box-shadow: none;
}
.table-shortcode {
cursor: text;
}
/* Default table styling */
.tablepress {
border-collapse: collapse;
border-spacing: 0;
width: 100%;
margin-bottom: 1em;
border: none;
}
.tablepress td,
.tablepress th {
padding: 8px;
border: none;
background: none;
text-align: {$rtl_align};
}
.tablepress tbody td {
vertical-align: top;
}
.tablepress tbody tr td,
.tablepress tfoot tr th {
border-top: 1px solid #dddddd;
}
.tablepress tbody tr:first-child td {
border-top: 0;
}
.tablepress thead tr th {
border-bottom: 1px solid #dddddd;
}
.tablepress thead th,
.tablepress tfoot th {
background-color: #d9edf7;
font-weight: bold;
vertical-align: middle;
}
.tablepress tbody tr.odd td {
background-color: #f9f9f9;
}
.tablepress tbody tr.even td {
background-color: #ffffff;
}
.tablepress .row-hover tr:hover td {
background-color: #f3f3f3;
}
.tablepress img {
margin: 0;
padding: 0;
border: none;
max-width: none;
}
</style>
CSS;
}
} // class TablePress_Render