.
*/
public function shortcode_table( $shortcode_atts ) {
// For empty Shortcodes like [table] or [table /], an empty string is passed, see WP Core #26927.
$shortcode_atts = (array) $shortcode_atts;
$_render = TablePress::load_class( 'TablePress_Render', 'class-render.php', 'classes' );
$default_shortcode_atts = $_render->get_default_render_options();
/**
* Filter the available/default attributes for the [table] Shortcode.
*
* @since 1.0.0
*
* @param array $default_shortcode_atts The [table] Shortcode default attributes.
*/
$default_shortcode_atts = apply_filters( 'tablepress_shortcode_table_default_shortcode_atts', $default_shortcode_atts );
// Parse Shortcode attributes, only allow those that are specified.
$shortcode_atts = shortcode_atts( $default_shortcode_atts, $shortcode_atts ); // Optional third argument left out on purpose. Use filter in the next line instead.
/**
* Filter the attributes that were passed to the [table] Shortcode.
*
* @since 1.0.0
*
* @param array $shortcode_atts The attributes passed to the [table] Shortcode.
*/
$shortcode_atts = apply_filters( 'tablepress_shortcode_table_shortcode_atts', $shortcode_atts );
// Check, if a table with the given ID exists.
$table_id = preg_replace( '/[^a-zA-Z0-9_-]/', '', $shortcode_atts['id'] );
if ( ! TablePress::$model_table->table_exists( $table_id ) ) {
$message = "[table “{$table_id}” not found /]
\n";
/**
* Filter the "Table not found" message.
*
* @since 1.0.0
*
* @param string $message The "Table not found" message.
* @param string $table_id The current table ID.
*/
$message = apply_filters( 'tablepress_table_not_found_message', $message, $table_id );
return $message;
}
// Load table, with table data, options, and visibility settings.
$table = TablePress::$model_table->load( $table_id, true, true );
if ( is_wp_error( $table ) ) {
$message = "[table “{$table_id}” could not be loaded /]
\n";
/**
* Filter the "Table could not be loaded" message.
*
* @since 1.0.0
*
* @param string $message The "Table could not be loaded" message.
* @param string $table_id The current table ID.
* @param WP_Error $table The error object for the table.
*/
$message = apply_filters( 'tablepress_table_load_error_message', $message, $table_id, $table );
return $message;
}
if ( isset( $table['is_corrupted'] ) && $table['is_corrupted'] ) {
$message = "Attention: The internal data of table “{$table_id}” is corrupted!
";
/**
* Filter the "Table data is corrupted" message.
*
* @since 1.0.0
*
* @param string $message The "Table data is corrupted" message.
* @param string $table_id The current table ID.
* @param string $json_error The JSON error with information about the corrupted table.
*/
$message = apply_filters( 'tablepress_table_corrupted_message', $message, $table_id, $table['json_error'] );
return $message;
}
/**
* Filter whether the "datatables_custom_commands" Shortcode parameter is disabled.
*
* By default, the "datatables_custom_commands" Shortcode parameter is disabled for security reasons.
*
* @since 1.0.0
*
* @param bool $disable Whether to disable the "datatables_custom_commands" Shortcode parameter. Default true.
*/
if ( ! is_null( $shortcode_atts['datatables_custom_commands'] ) && apply_filters( 'tablepress_disable_custom_commands_shortcode_parameter', true ) ) {
$shortcode_atts['datatables_custom_commands'] = null;
}
// Determine options to use (if set in Shortcode, use those, otherwise use stored options, from the "Edit" screen).
$render_options = array();
foreach ( $shortcode_atts as $key => $value ) {
// We have to check this, because strings 'true' or 'false' are not recognized as boolean!
if ( is_string( $value ) && 'true' === strtolower( $value ) ) {
$render_options[ $key ] = true;
} elseif ( is_string( $value ) && 'false' === strtolower( $value ) ) {
$render_options[ $key ] = false;
} elseif ( is_null( $value ) && isset( $table['options'][ $key ] ) ) {
$render_options[ $key ] = $table['options'][ $key ];
} else {
$render_options[ $key ] = $value;
}
}
// Generate unique HTML ID, depending on how often this table has already been shown on this page.
if ( ! isset( $this->shown_tables[ $table_id ] ) ) {
$this->shown_tables[ $table_id ] = array(
'count' => 0,
'instances' => array(),
);
}
$this->shown_tables[ $table_id ]['count']++;
$count = $this->shown_tables[ $table_id ]['count'];
$render_options['html_id'] = "tablepress-{$table_id}";
if ( $count > 1 ) {
$render_options['html_id'] .= "-no-{$count}";
}
/**
* Filter the ID of the table HTML element.
*
* @since 1.0.0
*
* @param string $html_id The ID of the table HTML element.
* @param string $table_id The current table ID.
* @param string $count Number of copies of the table with this table ID on the page.
*/
$render_options['html_id'] = apply_filters( 'tablepress_html_id', $render_options['html_id'], $table_id, $count );
// Generate the "Edit Table" link.
$render_options['edit_table_url'] = '';
/**
* Filter whether the "Edit" link below the table shall be shown.
*
* The "Edit" link is only shown to logged-in users who possess the necessary capability to edit the table.
*
* @since 1.0.0
*
* @param bool $show Whether to show the "Edit" link below the table. Default true.
* @param string $table_id The current table ID.
*/
if ( is_user_logged_in() && apply_filters( 'tablepress_edit_link_below_table', true, $table['id'] ) && current_user_can( 'tablepress_edit_table', $table['id'] ) ) {
$render_options['edit_table_url'] = TablePress::url( array( 'action' => 'edit', 'table_id' => $table['id'] ) );
}
/**
* Filter the render options for the table.
*
* The render options are determined from the settings on a table's "Edit" screen and the Shortcode parameters.
*
* @since 1.0.0
*
* @param array $render_options The render options for the table.
* @param array $table The current table.
*/
$render_options = apply_filters( 'tablepress_table_render_options', $render_options, $table );
// Eventually add this table to list of tables which have a JS library enabled and thus are to be included in the script's call in the footer.
if ( $render_options['use_datatables'] && $render_options['table_head'] && count( $table['data'] ) > 1 ) {
// Get options for the DataTables JavaScript library from the table's render options.
$js_options = array();
foreach ( array( 'alternating_row_colors', 'datatables_sort', 'datatables_paginate',
'datatables_paginate', 'datatables_paginate_entries', 'datatables_lengthchange',
'datatables_filter', 'datatables_info', 'datatables_scrollx', 'datatables_scrolly',
'datatables_locale', 'datatables_custom_commands' ) as $option ) {
$js_options[ $option ] = $render_options[ $option ];
}
/**
* Filter the JavaScript options for the table.
*
* The JavaScript options are determined from the settings on a table's "Edit" screen and the Shortcode parameters.
* They are part of the render options and can be overwritten with Shortcode parameters.
*
* @since 1.0.0
*
* @param array $js_options The JavaScript options for the table.
* @param string $table_id The current table ID.
* @param array $render_options The render options for the table.
*/
$js_options = apply_filters( 'tablepress_table_js_options', $js_options, $table_id, $render_options );
$this->shown_tables[ $table_id ]['instances'][ $render_options['html_id'] ] = $js_options;
$this->_enqueue_datatables();
}
// Check if table output shall and can be loaded from the transient cache, otherwise generate the output.
if ( $render_options['cache_table_output'] && ! is_user_logged_in() ) {
// Hash the Render Options array to get a unique cache identifier.
$table_hash = md5( wp_json_encode( $render_options ) );
$transient_name = 'tablepress_' . $table_hash; // Attention: This string must not be longer than 45 characters!
$output = get_transient( $transient_name );
if ( false === $output || '' === $output ) {
// Render/generate the table HTML, as it was not found in the cache.
$_render->set_input( $table, $render_options );
$output = $_render->get_output();
// Save render output in a transient, set cache timeout to 24 hours.
set_transient( $transient_name, $output, DAY_IN_SECONDS );
// Update output caches list transient (necessary for cache invalidation upon table saving).
$caches_list_transient_name = 'tablepress_c_' . md5( $table_id );
$caches_list = get_transient( $caches_list_transient_name );
if ( false === $caches_list ) {
$caches_list = array();
} else {
$caches_list = (array) json_decode( $caches_list, true );
}
if ( ! in_array( $transient_name, $caches_list, true ) ) {
$caches_list[] = $transient_name;
}
set_transient( $caches_list_transient_name, wp_json_encode( $caches_list ), 2 * DAY_IN_SECONDS );
} else {
/**
* Filter the cache hit comment message.
*
* @since 1.0.0
*
* @param string $comment The cache hit comment message.
*/
$output .= apply_filters( 'tablepress_cache_hit_comment', "" );
}
} else {
// Render/generate the table HTML, as no cache is to be used.
$_render->set_input( $table, $render_options );
$output = $_render->get_output();
}
// Maybe print a list of used render options.
if ( $render_options['shortcode_debug'] && is_user_logged_in() ) {
$output .= '' . var_export( $render_options, true ) . '
';
}
return $output;
}
/**
* Handle Shortcode [table-info id= field= /] in the_content().
*
* @since 1.0.0
*
* @param array $shortcode_atts List of attributes that where included in the Shortcode.
* @return string Text that replaces the Shortcode (error message or asked-for information).
*/
public function shortcode_table_info( $shortcode_atts ) {
// For empty Shortcodes like [table-info] or [table-info /], an empty string is passed, see Core #26927.
$shortcode_atts = (array) $shortcode_atts;
// Parse Shortcode attributes, only allow those that are specified.
$default_shortcode_atts = array(
'id' => '',
'field' => '',
'format' => '',
);
/**
* Filter the available/default attributes for the [table-info] Shortcode.
*
* @since 1.0.0
*
* @param array $default_shortcode_atts The [table-info] Shortcode default attributes.
*/
$default_shortcode_atts = apply_filters( 'tablepress_shortcode_table_info_default_shortcode_atts', $default_shortcode_atts );
$shortcode_atts = shortcode_atts( $default_shortcode_atts, $shortcode_atts ); // Optional third argument left out on purpose. Use filter in the next line instead.
/**
* Filter the attributes that were passed to the [table-info] Shortcode.
*
* @since 1.0.0
*
* @param array $shortcode_atts The attributes passed to the [table-info] Shortcode.
*/
$shortcode_atts = apply_filters( 'tablepress_shortcode_table_info_shortcode_atts', $shortcode_atts );
/**
* Filter whether the output of the [table-info] Shortcode is overwritten/short-circuited.
*
* @since 1.0.0
*
* @param bool|string $overwrite Whether the [table-info] output is overwritten. Return false for the regular content, and a string to overwrite the output.
* @param array $shortcode_atts The attributes passed to the [table-info] Shortcode.
*/
$overwrite = apply_filters( 'tablepress_shortcode_table_info_overwrite', false, $shortcode_atts );
if ( $overwrite ) {
return $overwrite;
}
// Check, if a table with the given ID exists.
$table_id = preg_replace( '/[^a-zA-Z0-9_-]/', '', $shortcode_atts['id'] );
if ( ! TablePress::$model_table->table_exists( $table_id ) ) {
$message = "[table “{$table_id}” not found /]
\n";
/** This filter is documented in controllers/controller-frontend.php */
$message = apply_filters( 'tablepress_table_not_found_message', $message, $table_id );
return $message;
}
// Load table, with table data, options, and visibility settings.
$table = TablePress::$model_table->load( $table_id, true, true );
if ( is_wp_error( $table ) ) {
$message = "[table “{$table_id}” could not be loaded /]
\n";
/** This filter is documented in controllers/controller-frontend.php */
$message = apply_filters( 'tablepress_table_load_error_message', $message, $table_id, $table );
return $message;
}
$field = preg_replace( '/[^a-z_]/', '', strtolower( $shortcode_atts['field'] ) );
$format = preg_replace( '/[^a-z]/', '', strtolower( $shortcode_atts['format'] ) );
// Generate output, depending on what information (field) was asked for.
switch ( $field ) {
case 'name':
case 'description':
$output = $table[ $field ];
break;
case 'last_modified':
switch ( $format ) {
case 'raw':
$output = $table['last_modified'];
break;
case 'human':
$modified_timestamp = strtotime( $table['last_modified'] );
$current_timestamp = current_time( 'timestamp' );
$time_diff = $current_timestamp - $modified_timestamp;
// Time difference is only shown up to one day.
if ( $time_diff >= 0 && $time_diff < DAY_IN_SECONDS ) {
$output = sprintf( __( '%s ago', 'default' ), human_time_diff( $modified_timestamp, $current_timestamp ) ); // No `tablepress` text domain as translations are not loaded.
} else {
$output = TablePress::format_datetime( $table['last_modified'], 'mysql', '
' );
}
break;
case 'mysql':
default:
$output = TablePress::format_datetime( $table['last_modified'], 'mysql', ' ' );
break;
}
break;
case 'last_editor':
$output = TablePress::get_user_display_name( $table['options']['last_editor'] );
break;
case 'author':
$output = TablePress::get_user_display_name( $table['author'] );
break;
case 'number_rows':
$output = count( $table['data'] );
if ( 'raw' !== $format ) {
if ( $table['options']['table_head'] ) {
$output = $output - 1;
}
if ( $table['options']['table_foot'] ) {
$output = $output - 1;
}
}
break;
case 'number_columns':
$output = count( $table['data'][0] );
break;
default:
$output = "[table-info field “{$field}” not found in table “{$table_id}” /]
\n";
/**
* Filter the "table info field not found" message.
*
* @since 1.0.0
*
* @param string $output The "table info field not found" message.
* @param array $table The current table ID.
* @param string $field The field that was not found.
* @param string $format The return format for the field.
*/
$output = apply_filters( 'tablepress_table_info_not_found_message', $output, $table, $field, $format );
}
/**
* Filter the output of the [table-info] Shortcode.
*
* @since 1.0.0
*
* @param string $output The output of the [table-info] Shortcode.
* @param array $table The current table.
* @param array $shortcode_atts The attributes passed to the [table-info] Shortcode.
*/
$output = apply_filters( 'tablepress_shortcode_table_info_output', $output, $table, $shortcode_atts );
return $output;
}
/**
* Handle Shortcodes in text widgets, by temporarily removing all Shortcodes, registering only the plugin's two,
* running WP's Shortcode routines, and then restoring old behavior/Shortcodes.
*
* @since 1.0.0
*
* @global array $shortcode_tags WordPress container for storing Shortcode definitions.
*
* @param string $content Text content of the text widget, will be searched for one of TablePress's Shortcodes.
* @return string Text of the text widget, with eventually found Shortcodes having been replaced by corresponding output.
*/
public function widget_text_filter( $content ) {
global $shortcode_tags;
// Backup the currently registered Shortcodes and clear the global array.
$orig_shortcode_tags = $shortcode_tags;
$shortcode_tags = array();
// Register TablePress's Shortcodes (which are then the only ones registered).
add_shortcode( TablePress::$shortcode_info, array( $this, 'shortcode_table_info' ) );
add_shortcode( TablePress::$shortcode, array( $this, 'shortcode_table' ) );
// Run the WP Shortcode routines on the widget text (i.e. search for TablePress's Shortcodes).
$content = do_shortcode( $content );
// Restore the original Shortcodes (which includes TablePress's Shortcodes, for use in posts and pages).
$shortcode_tags = $orig_shortcode_tags;
return $content;
}
/**
* Expand WP Search to also find posts and pages that have a search term in a table that is shown in them.
*
* This is done by looping through all search terms and TablePress tables and searching there for the search term,
* saving all tables's IDs that have a search term and then expanding the WP query to search for posts or pages that have the
* Shortcode for one of these tables in their content.
*
* @since 1.0.0
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param string $search_sql Current part of the "WHERE" clause of the SQL statement used to get posts/pages from the WP database that is related to searching.
* @return string Eventually extended SQL "WHERE" clause, to also find posts/pages with Shortcodes in them.
*/
public function posts_search_filter( $search_sql ) {
global $wpdb;
if ( ! is_search() || ! is_main_query() ) {
return $search_sql;
}
// Get variable that contains all search terms, parsed from $_GET['s'] by WP.
$search_terms = get_query_var( 'search_terms' );
if ( empty( $search_terms ) || ! is_array( $search_terms ) ) {
return $search_sql;
}
// Load all table IDs and prime post meta cache for cached access to options and visibility settings of the tables, don't run filter hook.
$table_ids = TablePress::$model_table->load_all( true, false );
// Array of all search words that were found, and the table IDs where they were found.
$query_result = array();
foreach ( $table_ids as $table_id ) {
// Load table, with table data, options, and visibility settings.
$table = TablePress::$model_table->load( $table_id, true, true );
if ( isset( $table['is_corrupted'] ) && $table['is_corrupted'] ) {
// Do not search in corrupted tables.
continue;
}
foreach ( $search_terms as $search_term ) {
if ( ( $table['options']['print_name'] && false !== stripos( $table['name'], $search_term ) )
|| ( $table['options']['print_description'] && false !== stripos( $table['description'], $search_term ) ) ) {
// Found the search term in the name or description (and they are shown).
$query_result[ $search_term ][] = $table_id; // Add table ID to result list.
// No need to continue searching this search term in this table.
continue;
}
// Search search term in visible table cells (without taking Shortcode parameters into account!).
foreach ( $table['data'] as $row_idx => $table_row ) {
if ( 0 === $table['visibility']['rows'][ $row_idx ] ) {
// Row is hidden, so don't search in it.
continue;
}
foreach ( $table_row as $col_idx => $table_cell ) {
if ( 0 === $table['visibility']['columns'][ $col_idx ] ) {
// Column is hidden, so don't search in it.
continue;
}
// @TODO: Cells are not evaluated here, so math formulas are searched.
if ( false !== stripos( $table_cell, $search_term ) ) {
// Found the search term in the cell content.
$query_result[ $search_term ][] = $table_id; // Add table ID to result list
// No need to continue searching this search term in this table.
continue 3;
}
}
}
}
}
// For all found table IDs for each search term, add additional OR statement to the SQL "WHERE" clause.
// If $_GET['exact'] is set, WordPress doesn't use % in SQL LIKE clauses.
$exact = get_query_var( 'exact' );
$n = ( empty( $exact ) ) ? '%' : '';
foreach ( $query_result as $search_term => $table_ids ) {
$search_term = esc_sql( $wpdb->esc_like( $search_term ) );
$old_or = "OR ({$wpdb->posts}.post_content LIKE '{$n}{$search_term}{$n}')";
$table_ids = implode( '|', $table_ids );
$regexp = '\\\\[' . TablePress::$shortcode . ' id=(["\\\']?)(' . $table_ids . ')([\]"\\\' /])'; // ' needs to be single escaped, [ double escaped (with \\) in mySQL
$new_or = $old_or . " OR ({$wpdb->posts}.post_content REGEXP '{$regexp}')";
$search_sql = str_replace( $old_or, $new_or, $search_sql );
}
return $search_sql;
}
} // class TablePress_Frontend_Controller