PluginProbe
TablePress – Tables in WordPress made easy / 1.12
TablePress – Tables in WordPress made easy v1.12
3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 2.4.4 All 44 releases
tablepress / controllers / controller-frontend.php

controller-frontend.php in TablePress – Tables in WordPress made easy 1.12, at controllers/controller-frontend.php

903 lines 36.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Frontend Controller for TablePress with functionality for the frontend
4 *
5 * @package TablePress
6 * @subpackage Controllers
7 * @author Tobias Bäthge
8 * @since 1.0.0
9 */
10
11 // Prohibit direct script loading.
12 defined( 'ABSPATH' ) || die( 'No direct script access allowed!' );
13
14 /**
15 * Frontend Controller class, extends Base Controller Class
16 * @package TablePress
17 * @subpackage Controllers
18 * @author Tobias Bäthge
19 * @since 1.0.0
20 */
21 class TablePress_Frontend_Controller extends TablePress_Controller {
22
23 /**
24 * List of tables that are shown for the current request.
25 *
26 * @since 1.0.0
27 * @var array
28 */
29 protected $shown_tables = array();
30
31 /**
32 * Initiate Frontend functionality.
33 *
34 * @since 1.0.0
35 */
36 public function __construct() {
37 parent::__construct();
38
39 /**
40 * Filter whether the TablePress Default CSS code shall be loaded.
41 *
42 * @since 1.0.0
43 *
44 * @param bool $use Whether the Default CSS shall be loaded. Default true.
45 */
46 if ( apply_filters( 'tablepress_use_default_css', true ) || TablePress::$model_options->get( 'use_custom_css' ) ) {
47 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_css' ) );
48 }
49
50 // Add DataTables invocation calls.
51 add_action( 'wp_print_footer_scripts', array( $this, 'add_datatables_calls' ), 11 ); // after inclusion of files
52
53 // Remove WP-Table Reloaded Shortcodes and CSS, and add TablePress Shortcodes.
54 add_action( 'init', array( $this, 'init_shortcodes' ), 20 ); // run on priority 20 as WP-Table Reloaded Shortcodes are registered at priority 10
55
56 /**
57 * Filter whether the WordPress search shall also search TablePress tables.
58 *
59 * @since 1.0.0
60 *
61 * @param bool $search Whether the TablePress tables shall be searched. Default true.
62 */
63 if ( apply_filters( 'tablepress_wp_search_integration', true ) ) {
64 // Extend WordPress Search to also find posts/pages that have a table with the one of the search terms in title (if shown), description (if shown), or content.
65 add_filter( 'posts_search', array( $this, 'posts_search_filter' ) );
66 }
67
68 /**
69 * Load TablePress Template Tag functions.
70 */
71 require_once TABLEPRESS_ABSPATH . 'controllers/template-tag-functions.php';
72 }
73
74 /**
75 * Register TablePress Shortcodes, after removing WP-Table Reloaded Shortcodes.
76 *
77 * @since 1.0.0
78 */
79 public function init_shortcodes() {
80 // Remove previously registered [table /] Shortcodes (e.g. from WP-Table Reloaded), as these would otherwise be used instead of TablePress's Shortcodes.
81 remove_shortcode( TablePress::$shortcode );
82 remove_shortcode( TablePress::$shortcode_info );
83 // Dequeue WP-Table Relaoded Default CSS, as it can influence TablePress table styling.
84 if ( isset( $GLOBALS['WP_Table_Reloaded_Frontend'] ) ) {
85 remove_action( 'wp_head', array( $GLOBALS['WP_Table_Reloaded_Frontend'], 'add_frontend_css' ) );
86 }
87
88 add_shortcode( TablePress::$shortcode, array( $this, 'shortcode_table' ) );
89 add_shortcode( TablePress::$shortcode_info, array( $this, 'shortcode_table_info' ) );
90 }
91
92 /**
93 * Enqueue CSS files for default CSS and "Custom CSS" (if desired).
94 *
95 * @since 1.0.0
96 */
97 public function enqueue_css() {
98 /** This filter is documented in controllers/controller-frontend.php */
99 $use_default_css = apply_filters( 'tablepress_use_default_css', true );
100 $custom_css = TablePress::$model_options->get( 'custom_css' );
101 $use_custom_css = ( TablePress::$model_options->get( 'use_custom_css' ) && '' !== $custom_css );
102 $use_custom_css_file = ( $use_custom_css && TablePress::$model_options->get( 'use_custom_css_file' ) );
103 /**
104 * Filter the "Custom CSS" version number that is appended to the enqueued CSS files
105 *
106 * @since 1.0.0
107 *
108 * @param int $version The "Custom CSS" version.
109 */
110 $custom_css_version = apply_filters( 'tablepress_custom_css_version', TablePress::$model_options->get( 'custom_css_version' ) );
111
112 $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' );
113
114 // Determine Default CSS URL.
115 $rtl = ( is_rtl() ) ? '-rtl' : '';
116 $suffix = SCRIPT_DEBUG ? '' : '.min';
117 $unfiltered_default_css_url = plugins_url( "css/default{$rtl}{$suffix}.css", TABLEPRESS__FILE__ );
118 /**
119 * Filter the URL from which the TablePress Default CSS file is loaded.
120 *
121 * @since 1.0.0
122 *
123 * @param string $unfiltered_default_css_url URL of the TablePress Default CSS file.
124 */
125 $default_css_url = apply_filters( 'tablepress_default_css_url', $unfiltered_default_css_url );
126
127 $use_custom_css_combined_file = ( $use_default_css && $use_custom_css_file && ! SCRIPT_DEBUG && ! is_rtl() && $unfiltered_default_css_url === $default_css_url && $tablepress_css->load_custom_css_from_file( 'combined' ) );
128
129 if ( $use_custom_css_combined_file ) {
130 $custom_css_combined_url = $tablepress_css->get_custom_css_location( 'combined', 'url' );
131 // Need to use 'tablepress-default' instead of 'tablepress-combined' to not break existing TablePress Extensions.
132 wp_enqueue_style( 'tablepress-default', $custom_css_combined_url, array(), $custom_css_version );
133 } else {
134 $custom_css_dependencies = array();
135 if ( $use_default_css ) {
136 wp_enqueue_style( 'tablepress-default', $default_css_url, array(), TablePress::version );
137 // Add dependency to make sure that Custom CSS is printed after Default CSS.
138 $custom_css_dependencies[] = 'tablepress-default';
139 }
140
141 $use_custom_css_minified_file = ( $use_custom_css_file && ! SCRIPT_DEBUG && $tablepress_css->load_custom_css_from_file( 'minified' ) );
142 if ( $use_custom_css_minified_file ) {
143 $custom_css_minified_url = $tablepress_css->get_custom_css_location( 'minified', 'url' );
144 wp_enqueue_style( 'tablepress-custom', $custom_css_minified_url, $custom_css_dependencies, $custom_css_version );
145 return;
146 }
147
148 $use_custom_css_normal_file = ( $use_custom_css_file && $tablepress_css->load_custom_css_from_file( 'normal' ) );
149 if ( $use_custom_css_normal_file ) {
150 $custom_css_normal_url = $tablepress_css->get_custom_css_location( 'normal', 'url' );
151 wp_enqueue_style( 'tablepress-custom', $custom_css_normal_url, $custom_css_dependencies, $custom_css_version );
152 return;
153 }
154
155 if ( $use_custom_css ) {
156 // Get "Custom CSS" from options, try minified Custom CSS first,
157 $custom_css_minified = TablePress::$model_options->get( 'custom_css_minified' );
158 if ( ! empty( $custom_css_minified ) ) {
159 $custom_css = $custom_css_minified;
160 }
161 /**
162 * Filter the "Custom CSS" code that is to be loaded as inline CSS.
163 *
164 * @since 1.0.0
165 *
166 * @param string $custom_css The "Custom CSS" code.
167 */
168 $custom_css = apply_filters( 'tablepress_custom_css', $custom_css );
169 if ( ! empty( $custom_css ) ) {
170 // wp_add_inline_style() requires a loaded CSS file, so we have to work around that if "Default CSS" is disabled,
171 if ( $use_default_css ) {
172 // Handle of the file to which the <style> shall be appended.
173 wp_add_inline_style( 'tablepress-default', $custom_css );
174 } else {
175 add_action( 'wp_head', array( $this, '_print_custom_css' ), 8 ); // priority 8 to hook in right after WP_Styles has been processed
176 }
177 }
178 }
179 }
180 }
181
182 /**
183 * Print "Custom CSS" to "wp_head" inline.
184 *
185 * This is necessary if "Default CSS" is off, and saving "Custom CSS" to a file is not possible.
186 *
187 * @since 1.0.0
188 */
189 public function _print_custom_css() {
190 // Get "Custom CSS" from options, try minified Custom CSS first.
191 $custom_css = TablePress::$model_options->get( 'custom_css_minified' );
192 if ( empty( $custom_css ) ) {
193 $custom_css = TablePress::$model_options->get( 'custom_css' );
194 }
195 /** This filter is documented in controllers/controller-frontend.php */
196 $custom_css = apply_filters( 'tablepress_custom_css', $custom_css );
197 echo "<style type='text/css'>\n{$custom_css}\n</style>\n";
198 }
199
200 /**
201 * Enqueue the DataTables JavaScript library and its dependencies.
202 *
203 * @since 1.0.0
204 */
205 protected function _enqueue_datatables() {
206 $js_file = 'js/jquery.datatables.min.js';
207 $js_url = plugins_url( $js_file, TABLEPRESS__FILE__ );
208 /**
209 * Filter the URL from which the DataTables JavaScript library file is loaded.
210 *
211 * @since 1.0.0
212 *
213 * @param string $js_url URL of the DataTables JS library file.
214 * @param string $js_file Path and file name of the DataTables JS library file.
215 */
216 $js_url = apply_filters( 'tablepress_datatables_js_url', $js_url, $js_file );
217 wp_enqueue_script( 'tablepress-datatables', $js_url, array( 'jquery' ), TablePress::version, true );
218 }
219
220 /**
221 * Add JS code for invocation of DataTables JS library.
222 *
223 * @since 1.0.0
224 */
225 public function add_datatables_calls() {
226 if ( empty( $this->shown_tables ) ) {
227 // There are no tables with activated DataTables on the page that is currently rendered.
228 return;
229 }
230
231 // Storage for the DataTables languages.
232 $datatables_languages = array();
233 // Generate the specific JS commands, depending on chosen features on the "Edit" screen and the Shortcode parameters.
234 $commands = array();
235
236 foreach ( $this->shown_tables as $table_id => $table_store ) {
237 if ( empty( $table_store['instances'] ) ) {
238 continue;
239 }
240 foreach ( $table_store['instances'] as $html_id => $js_options ) {
241 $parameters = array();
242
243 // Settle dependencies/conflicts between certain features.
244 if ( false !== $js_options['datatables_scrolly'] ) { // not necessarily a boolean!
245 // Vertical scrolling and pagination don't work together.
246 $js_options['datatables_paginate'] = false;
247 }
248 // Sanitize, as it may come from a Shortcode attribute.
249 $js_options['datatables_paginate_entries'] = intval( $js_options['datatables_paginate_entries'] );
250
251 // DataTables language/translation handling.
252 /**
253 * Filter the locale/language for the DataTables JavaScript library.
254 *
255 * @since 1.0.0
256 *
257 * @param string $locale The DataTables JS library locale.
258 * @param string $table_id The current table ID.
259 */
260 $datatables_locale = apply_filters( 'tablepress_datatables_locale', $js_options['datatables_locale'], $table_id );
261 // Only do the expensive language file checks if they haven't been done yet.
262 if ( ! isset( $datatables_languages[ $datatables_locale ] ) ) {
263 $orig_language_file = TABLEPRESS_ABSPATH . "i18n/datatables/lang-{$datatables_locale}.json";
264 /**
265 * Filter the language file for the DataTables JavaScript library.
266 *
267 * @since 1.0.0
268 *
269 * @param string $orig_language_file Language file for the DataTables JS library.
270 * @param string $datatables_locale Current locale/language for the DataTables JS library.
271 * @param string $path Path of the language file.
272 */
273 $language_file = apply_filters( 'tablepress_datatables_language_file', $orig_language_file, $datatables_locale, TABLEPRESS_ABSPATH ); // Make sure to check file_exists( $new_file ) when using this filter!
274 // Load translation if it's not "en_US" (included as the default in DataTables) and the language file exists, or if the filter was used to change the language file.
275 if ( ( 'en_US' !== $datatables_locale && file_exists( $language_file ) )
276 || ( $orig_language_file !== $language_file ) ) {
277 $datatables_languages[ $datatables_locale ] = $language_file;
278 }
279 }
280 // If translation is registered to have its strings added to the JS, add corresponding parameter to DataTables call.
281 if ( isset( $datatables_languages[ $datatables_locale ] ) ) {
282 $parameters['language'] = '"language":DataTables_language["' . $datatables_locale . '"]';
283 }
284 // These parameters need to be added for performance gain or to overwrite unwanted default behavior.
285 if ( $js_options['datatables_sort'] ) {
286 // No initial sort.
287 $parameters['order'] = '"order":[]';
288 // Don't add additional classes, to speed up sorting.
289 $parameters['orderClasses'] = '"orderClasses":false';
290 }
291 // Alternating row colors is default, so remove them if not wanted with [].
292 $parameters['stripeClasses'] = '"stripeClasses":' . ( ( $js_options['alternating_row_colors'] ) ? '["even","odd"]' : '[]' );
293 // The following options are activated by default, so we only need to "false" them if we don't want them, but don't need to "true" them if we do.
294 if ( ! $js_options['datatables_sort'] ) {
295 $parameters['ordering'] = '"ordering":false';
296 }
297 if ( $js_options['datatables_paginate'] ) {
298 $parameters['pagingType'] = '"pagingType":"simple"';
299 if ( $js_options['datatables_lengthchange'] ) {
300 $length_menu = array( 10, 25, 50, 100 );
301 if ( ! in_array( $js_options['datatables_paginate_entries'], $length_menu, true ) ) {
302 $length_menu[] = $js_options['datatables_paginate_entries'];
303 sort( $length_menu, SORT_NUMERIC );
304 $parameters['lengthMenu'] = '"lengthMenu":[' . implode( ',', $length_menu ) . ']';
305 }
306 } else {
307 $parameters['lengthChange'] = '"lengthChange":false';
308 }
309 if ( 10 !== $js_options['datatables_paginate_entries'] ) {
310 $parameters['pageLength'] = '"pageLength":' . $js_options['datatables_paginate_entries'];
311 }
312 } else {
313 $parameters['paging'] = '"paging":false';
314 }
315 if ( ! $js_options['datatables_filter'] ) {
316 $parameters['searching'] = '"searching":false';
317 }
318 if ( ! $js_options['datatables_info'] ) {
319 $parameters['info'] = '"info":false';
320 }
321 if ( $js_options['datatables_scrollx'] ) {
322 $parameters['scrollX'] = '"scrollX":true';
323 }
324 if ( false !== $js_options['datatables_scrolly'] ) {
325 $parameters['scrollY'] = '"scrollY":"' . preg_replace( '#[^0-9a-z.%]#', '', $js_options['datatables_scrolly'] ) . '"';
326 $parameters['scrollCollapse'] = '"scrollCollapse":true';
327 }
328 if ( ! empty( $js_options['datatables_custom_commands'] ) ) {
329 $parameters['custom_commands'] = $js_options['datatables_custom_commands'];
330 }
331
332 /**
333 * Filter the parameters that are passed to the DataTables JavaScript library.
334 *
335 * @since 1.0.0
336 *
337 * @param array $parameters The parameters for the DataTables JS library.
338 * @param string $table_id The current table ID.
339 * @param string $html_id The ID of the table HTML element.
340 * @param array $js_options The options for the JS library.
341 */
342 $parameters = apply_filters( 'tablepress_datatables_parameters', $parameters, $table_id, $html_id, $js_options );
343
344 // If an existing parameter (in the from `"parameter":`) is set in the "Custom Commands", remove its default value.
345 if ( isset( $parameters['custom_commands'] ) ) {
346 foreach ( array_keys( $parameters ) as $maybe_overwritten_parameter ) {
347 if ( false !== strpos( $parameters['custom_commands'], "\"{$maybe_overwritten_parameter}\":" ) ) {
348 unset( $parameters[ $maybe_overwritten_parameter ] );
349 }
350 }
351 }
352
353 $parameters = implode( ',', $parameters );
354 $parameters = ( ! empty( $parameters ) ) ? '{' . $parameters . '}' : '';
355
356 $command = "$('#{$html_id}').dataTable({$parameters});";
357 /**
358 * Filter the JavaScript command that invokes the DataTables JavaScript library on one table.
359 *
360 * @since 1.0.0
361 *
362 * @param string $command The JS command for the DataTables JS library.
363 * @param string $html_id The ID of the table HTML element.
364 * @param array $parameters The parameters for the DataTables JS library.
365 * @param string $table_id The current table ID.
366 * @param array $js_options The options for the JS library.
367 */
368 $command = apply_filters( 'tablepress_datatables_command', $command, $html_id, $parameters, $table_id, $js_options );
369 if ( ! empty( $command ) ) {
370 $commands[] = $command;
371 }
372 }
373 }
374
375 $commands = implode( "\n", $commands );
376 /**
377 * Filter the JavaScript commands that invoke the DataTables JavaScript library on all tables on the page.
378 *
379 * @since 1.0.0
380 *
381 * @param string $commands The JS commands for the DataTables JS library.
382 */
383 $commands = apply_filters( 'tablepress_all_datatables_commands', $commands );
384 if ( empty( $commands ) ) {
385 return;
386 }
387
388 // DataTables language/translation handling.
389 $datatables_strings = '';
390 foreach ( $datatables_languages as $locale => $language_file ) {
391 $strings = file_get_contents( $language_file );
392 // Remove unnecessary white space.
393 $strings = str_replace( array( "\n", "\r", "\t" ), '', $strings );
394 $datatables_strings .= "DataTables_language[\"{$locale}\"]={$strings};\n";
395 }
396 if ( ! empty( $datatables_strings ) ) {
397 $datatables_strings = "var DataTables_language={};\n" . $datatables_strings;
398 }
399
400 // Echo DataTables strings and JS calls.
401 echo <<<JS
402 <script type="text/javascript">
403 jQuery(document).ready(function($){
404 {$datatables_strings}{$commands}
405 });
406 </script>
407 JS;
408 }
409
410 /**
411 * Handle Shortcode [table id=<ID> /] in `the_content()`.
412 *
413 * @since 1.0.0
414 *
415 * @param array $shortcode_atts List of attributes that where included in the Shortcode.
416 * @return string Resulting HTML code for the table with the ID <ID>.
417 */
418 public function shortcode_table( $shortcode_atts ) {
419 // For empty Shortcodes like [table] or [table /], an empty string is passed, see WP Core #26927.
420 $shortcode_atts = (array) $shortcode_atts;
421
422 $_render = TablePress::load_class( 'TablePress_Render', 'class-render.php', 'classes' );
423
424 $default_shortcode_atts = $_render->get_default_render_options();
425 /**
426 * Filter the available/default attributes for the [table] Shortcode.
427 *
428 * @since 1.0.0
429 *
430 * @param array $default_shortcode_atts The [table] Shortcode default attributes.
431 */
432 $default_shortcode_atts = apply_filters( 'tablepress_shortcode_table_default_shortcode_atts', $default_shortcode_atts );
433 // Parse Shortcode attributes, only allow those that are specified.
434 $shortcode_atts = shortcode_atts( $default_shortcode_atts, $shortcode_atts ); // Optional third argument left out on purpose. Use filter in the next line instead.
435 /**
436 * Filter the attributes that were passed to the [table] Shortcode.
437 *
438 * @since 1.0.0
439 *
440 * @param array $shortcode_atts The attributes passed to the [table] Shortcode.
441 */
442 $shortcode_atts = apply_filters( 'tablepress_shortcode_table_shortcode_atts', $shortcode_atts );
443
444 // Check, if a table with the given ID exists.
445 $table_id = preg_replace( '/[^a-zA-Z0-9_-]/', '', $shortcode_atts['id'] );
446 if ( ! TablePress::$model_table->table_exists( $table_id ) ) {
447 $message = "[table &#8220;{$table_id}&#8221; not found /]<br />\n";
448 /**
449 * Filter the "Table not found" message.
450 *
451 * @since 1.0.0
452 *
453 * @param string $message The "Table not found" message.
454 * @param string $table_id The current table ID.
455 */
456 $message = apply_filters( 'tablepress_table_not_found_message', $message, $table_id );
457 return $message;
458 }
459
460 // Load table, with table data, options, and visibility settings.
461 $table = TablePress::$model_table->load( $table_id, true, true );
462 if ( is_wp_error( $table ) ) {
463 $message = "[table &#8220;{$table_id}&#8221; could not be loaded /]<br />\n";
464 /**
465 * Filter the "Table could not be loaded" message.
466 *
467 * @since 1.0.0
468 *
469 * @param string $message The "Table could not be loaded" message.
470 * @param string $table_id The current table ID.
471 * @param WP_Error $table The error object for the table.
472 */
473 $message = apply_filters( 'tablepress_table_load_error_message', $message, $table_id, $table );
474 return $message;
475 }
476 if ( isset( $table['is_corrupted'] ) && $table['is_corrupted'] ) {
477 $message = "<div>Attention: The internal data of table &#8220;{$table_id}&#8221; is corrupted!</div>";
478 /**
479 * Filter the "Table data is corrupted" message.
480 *
481 * @since 1.0.0
482 *
483 * @param string $message The "Table data is corrupted" message.
484 * @param string $table_id The current table ID.
485 * @param string $json_error The JSON error with information about the corrupted table.
486 */
487 $message = apply_filters( 'tablepress_table_corrupted_message', $message, $table_id, $table['json_error'] );
488 return $message;
489 }
490
491 /**
492 * Filter whether the "datatables_custom_commands" Shortcode parameter is disabled.
493 *
494 * By default, the "datatables_custom_commands" Shortcode parameter is disabled for security reasons.
495 *
496 * @since 1.0.0
497 *
498 * @param bool $disable Whether to disable the "datatables_custom_commands" Shortcode parameter. Default true.
499 */
500 if ( ! is_null( $shortcode_atts['datatables_custom_commands'] ) && apply_filters( 'tablepress_disable_custom_commands_shortcode_parameter', true ) ) {
501 $shortcode_atts['datatables_custom_commands'] = null;
502 }
503
504 // Determine options to use (if set in Shortcode, use those, otherwise use stored options, from the "Edit" screen).
505 $render_options = array();
506 foreach ( $shortcode_atts as $key => $value ) {
507 // We have to check this, because strings 'true' or 'false' are not recognized as boolean!
508 if ( is_string( $value ) && 'true' === strtolower( $value ) ) {
509 $render_options[ $key ] = true;
510 } elseif ( is_string( $value ) && 'false' === strtolower( $value ) ) {
511 $render_options[ $key ] = false;
512 } elseif ( is_null( $value ) && isset( $table['options'][ $key ] ) ) {
513 $render_options[ $key ] = $table['options'][ $key ];
514 } else {
515 $render_options[ $key ] = $value;
516 }
517 }
518
519 // Generate unique HTML ID, depending on how often this table has already been shown on this page.
520 if ( ! isset( $this->shown_tables[ $table_id ] ) ) {
521 $this->shown_tables[ $table_id ] = array(
522 'count' => 0,
523 'instances' => array(),
524 );
525 }
526 $this->shown_tables[ $table_id ]['count']++;
527 $count = $this->shown_tables[ $table_id ]['count'];
528 $render_options['html_id'] = "tablepress-{$table_id}";
529 if ( $count > 1 ) {
530 $render_options['html_id'] .= "-no-{$count}";
531 }
532 /**
533 * Filter the ID of the table HTML element.
534 *
535 * @since 1.0.0
536 *
537 * @param string $html_id The ID of the table HTML element.
538 * @param string $table_id The current table ID.
539 * @param string $count Number of copies of the table with this table ID on the page.
540 */
541 $render_options['html_id'] = apply_filters( 'tablepress_html_id', $render_options['html_id'], $table_id, $count );
542
543 // Generate the "Edit Table" link.
544 $render_options['edit_table_url'] = '';
545 /**
546 * Filter whether the "Edit" link below the table shall be shown.
547 *
548 * The "Edit" link is only shown to logged-in users who possess the necessary capability to edit the table.
549 *
550 * @since 1.0.0
551 *
552 * @param bool $show Whether to show the "Edit" link below the table. Default true.
553 * @param string $table_id The current table ID.
554 */
555 if ( is_user_logged_in() && apply_filters( 'tablepress_edit_link_below_table', true, $table['id'] ) && current_user_can( 'tablepress_edit_table', $table['id'] ) ) {
556 $render_options['edit_table_url'] = TablePress::url( array( 'action' => 'edit', 'table_id' => $table['id'] ) );
557 }
558
559 /**
560 * Filter the render options for the table.
561 *
562 * The render options are determined from the settings on a table's "Edit" screen and the Shortcode parameters.
563 *
564 * @since 1.0.0
565 *
566 * @param array $render_options The render options for the table.
567 * @param array $table The current table.
568 */
569 $render_options = apply_filters( 'tablepress_table_render_options', $render_options, $table );
570
571 // 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.
572 if ( $render_options['use_datatables'] && $render_options['table_head'] && count( $table['data'] ) > 1 ) {
573 // Get options for the DataTables JavaScript library from the table's render options.
574 $js_options = array();
575 foreach ( array(
576 'alternating_row_colors',
577 'datatables_sort',
578 'datatables_paginate',
579 'datatables_paginate',
580 'datatables_paginate_entries',
581 'datatables_lengthchange',
582 'datatables_filter',
583 'datatables_info',
584 'datatables_scrollx',
585 'datatables_scrolly',
586 'datatables_locale',
587 'datatables_custom_commands',
588 ) as $option ) {
589 $js_options[ $option ] = $render_options[ $option ];
590 }
591 /**
592 * Filter the JavaScript options for the table.
593 *
594 * The JavaScript options are determined from the settings on a table's "Edit" screen and the Shortcode parameters.
595 * They are part of the render options and can be overwritten with Shortcode parameters.
596 *
597 * @since 1.0.0
598 *
599 * @param array $js_options The JavaScript options for the table.
600 * @param string $table_id The current table ID.
601 * @param array $render_options The render options for the table.
602 */
603 $js_options = apply_filters( 'tablepress_table_js_options', $js_options, $table_id, $render_options );
604 $this->shown_tables[ $table_id ]['instances'][ $render_options['html_id'] ] = $js_options;
605 $this->_enqueue_datatables();
606 }
607
608 // Check if table output shall and can be loaded from the transient cache, otherwise generate the output.
609 if ( $render_options['cache_table_output'] && ! is_user_logged_in() ) {
610 // Hash the Render Options array to get a unique cache identifier.
611 $table_hash = md5( wp_json_encode( $render_options, TABLEPRESS_JSON_OPTIONS ) );
612 $transient_name = 'tablepress_' . $table_hash; // Attention: This string must not be longer than 45 characters!
613 $output = get_transient( $transient_name );
614 if ( false === $output || '' === $output ) {
615 // Render/generate the table HTML, as it was not found in the cache.
616 $_render->set_input( $table, $render_options );
617 $output = $_render->get_output();
618 // Save render output in a transient, set cache timeout to 24 hours.
619 set_transient( $transient_name, $output, DAY_IN_SECONDS );
620 // Update output caches list transient (necessary for cache invalidation upon table saving).
621 $caches_list_transient_name = 'tablepress_c_' . md5( $table_id );
622 $caches_list = get_transient( $caches_list_transient_name );
623 if ( false === $caches_list ) {
624 $caches_list = array();
625 } else {
626 $caches_list = (array) json_decode( $caches_list, true );
627 }
628 if ( ! in_array( $transient_name, $caches_list, true ) ) {
629 $caches_list[] = $transient_name;
630 }
631 set_transient( $caches_list_transient_name, wp_json_encode( $caches_list, TABLEPRESS_JSON_OPTIONS ), 2 * DAY_IN_SECONDS );
632 } else {
633 /**
634 * Filter the cache hit comment message.
635 *
636 * @since 1.0.0
637 *
638 * @param string $comment The cache hit comment message.
639 */
640 $output .= apply_filters( 'tablepress_cache_hit_comment', "<!-- #{$render_options['html_id']} from cache -->" );
641 }
642 } else {
643 // Render/generate the table HTML, as no cache is to be used.
644 $_render->set_input( $table, $render_options );
645 $output = $_render->get_output();
646 }
647
648 // Maybe print a list of used render options.
649 if ( $render_options['shortcode_debug'] && is_user_logged_in() ) {
650 $output .= '<pre>' . var_export( $render_options, true ) . '</pre>';
651 }
652
653 return $output;
654 }
655
656 /**
657 * Handle Shortcode [table-info id=<ID> field=<name> /] in the_content().
658 *
659 * @since 1.0.0
660 *
661 * @param array $shortcode_atts List of attributes that where included in the Shortcode.
662 * @return string Text that replaces the Shortcode (error message or asked-for information).
663 */
664 public function shortcode_table_info( $shortcode_atts ) {
665 // For empty Shortcodes like [table-info] or [table-info /], an empty string is passed, see Core #26927.
666 $shortcode_atts = (array) $shortcode_atts;
667
668 // Parse Shortcode attributes, only allow those that are specified.
669 $default_shortcode_atts = array(
670 'id' => '',
671 'field' => '',
672 'format' => '',
673 );
674 /**
675 * Filter the available/default attributes for the [table-info] Shortcode.
676 *
677 * @since 1.0.0
678 *
679 * @param array $default_shortcode_atts The [table-info] Shortcode default attributes.
680 */
681 $default_shortcode_atts = apply_filters( 'tablepress_shortcode_table_info_default_shortcode_atts', $default_shortcode_atts );
682 $shortcode_atts = shortcode_atts( $default_shortcode_atts, $shortcode_atts ); // Optional third argument left out on purpose. Use filter in the next line instead.
683 /**
684 * Filter the attributes that were passed to the [table-info] Shortcode.
685 *
686 * @since 1.0.0
687 *
688 * @param array $shortcode_atts The attributes passed to the [table-info] Shortcode.
689 */
690 $shortcode_atts = apply_filters( 'tablepress_shortcode_table_info_shortcode_atts', $shortcode_atts );
691
692 /**
693 * Filter whether the output of the [table-info] Shortcode is overwritten/short-circuited.
694 *
695 * @since 1.0.0
696 *
697 * @param bool|string $overwrite Whether the [table-info] output is overwritten. Return false for the regular content, and a string to overwrite the output.
698 * @param array $shortcode_atts The attributes passed to the [table-info] Shortcode.
699 */
700 $overwrite = apply_filters( 'tablepress_shortcode_table_info_overwrite', false, $shortcode_atts );
701 if ( $overwrite ) {
702 return $overwrite;
703 }
704
705 // Check, if a table with the given ID exists.
706 $table_id = preg_replace( '/[^a-zA-Z0-9_-]/', '', $shortcode_atts['id'] );
707 if ( ! TablePress::$model_table->table_exists( $table_id ) ) {
708 $message = "[table &#8220;{$table_id}&#8221; not found /]<br />\n";
709 /** This filter is documented in controllers/controller-frontend.php */
710 $message = apply_filters( 'tablepress_table_not_found_message', $message, $table_id );
711 return $message;
712 }
713
714 // Load table, with table data, options, and visibility settings.
715 $table = TablePress::$model_table->load( $table_id, true, true );
716 if ( is_wp_error( $table ) ) {
717 $message = "[table &#8220;{$table_id}&#8221; could not be loaded /]<br />\n";
718 /** This filter is documented in controllers/controller-frontend.php */
719 $message = apply_filters( 'tablepress_table_load_error_message', $message, $table_id, $table );
720 return $message;
721 }
722
723 $field = preg_replace( '/[^a-z_]/', '', strtolower( $shortcode_atts['field'] ) );
724 $format = preg_replace( '/[^a-z]/', '', strtolower( $shortcode_atts['format'] ) );
725
726 // Generate output, depending on what information (field) was asked for.
727 switch ( $field ) {
728 case 'name':
729 case 'description':
730 $output = $table[ $field ];
731 break;
732 case 'last_modified':
733 switch ( $format ) {
734 case 'raw':
735 $output = $table['last_modified'];
736 break;
737 case 'human':
738 $modified_timestamp = strtotime( $table['last_modified'] );
739 $current_timestamp = current_time( 'timestamp' );
740 $time_diff = $current_timestamp - $modified_timestamp;
741 // Time difference is only shown up to one day.
742 if ( $time_diff >= 0 && $time_diff < DAY_IN_SECONDS ) {
743 $output = sprintf( __( '%s ago', 'default' ), human_time_diff( $modified_timestamp, $current_timestamp ) );
744 } else {
745 $output = TablePress::format_datetime( $table['last_modified'], 'mysql', '<br />' );
746 }
747 break;
748 case 'date':
749 $modified_timestamp = strtotime( $table['last_modified'] );
750 $output = date_i18n( get_option( 'date_format' ), $modified_timestamp );
751 break;
752 case 'time':
753 $modified_timestamp = strtotime( $table['last_modified'] );
754 $output = date_i18n( get_option( 'time_format' ), $modified_timestamp );
755 break;
756 case 'mysql':
757 default:
758 $output = TablePress::format_datetime( $table['last_modified'], 'mysql', ' ' );
759 break;
760 }
761 break;
762 case 'last_editor':
763 $output = TablePress::get_user_display_name( $table['options']['last_editor'] );
764 break;
765 case 'author':
766 $output = TablePress::get_user_display_name( $table['author'] );
767 break;
768 case 'number_rows':
769 $output = count( $table['data'] );
770 if ( 'raw' !== $format ) {
771 if ( $table['options']['table_head'] ) {
772 $output = $output - 1;
773 }
774 if ( $table['options']['table_foot'] ) {
775 $output = $output - 1;
776 }
777 }
778 break;
779 case 'number_columns':
780 $output = count( $table['data'][0] );
781 break;
782 default:
783 $output = "[table-info field &#8220;{$field}&#8221; not found in table &#8220;{$table_id}&#8221; /]<br />\n";
784 /**
785 * Filter the "table info field not found" message.
786 *
787 * @since 1.0.0
788 *
789 * @param string $output The "table info field not found" message.
790 * @param array $table The current table ID.
791 * @param string $field The field that was not found.
792 * @param string $format The return format for the field.
793 */
794 $output = apply_filters( 'tablepress_table_info_not_found_message', $output, $table, $field, $format );
795 }
796
797 /**
798 * Filter the output of the [table-info] Shortcode.
799 *
800 * @since 1.0.0
801 *
802 * @param string $output The output of the [table-info] Shortcode.
803 * @param array $table The current table.
804 * @param array $shortcode_atts The attributes passed to the [table-info] Shortcode.
805 */
806 $output = apply_filters( 'tablepress_shortcode_table_info_output', $output, $table, $shortcode_atts );
807 return $output;
808 }
809
810 /**
811 * Expand WP Search to also find posts and pages that have a search term in a table that is shown in them.
812 *
813 * This is done by looping through all search terms and TablePress tables and searching there for the search term,
814 * 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
815 * Shortcode for one of these tables in their content.
816 *
817 * @since 1.0.0
818 *
819 * @global wpdb $wpdb WordPress database abstraction object.
820 *
821 * @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.
822 * @return string Eventually extended SQL "WHERE" clause, to also find posts/pages with Shortcodes in them.
823 */
824 public function posts_search_filter( $search_sql ) {
825 global $wpdb;
826
827 if ( ! is_search() || ! is_main_query() ) {
828 return $search_sql;
829 }
830
831 // Get variable that contains all search terms, parsed from $_GET['s'] by WP.
832 $search_terms = get_query_var( 'search_terms' );
833 if ( empty( $search_terms ) || ! is_array( $search_terms ) ) {
834 return $search_sql;
835 }
836
837 // 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.
838 $table_ids = TablePress::$model_table->load_all( true, false );
839 // Array of all search words that were found, and the table IDs where they were found.
840 $query_result = array();
841
842 foreach ( $table_ids as $table_id ) {
843 // Load table, with table data, options, and visibility settings.
844 $table = TablePress::$model_table->load( $table_id, true, true );
845
846 if ( isset( $table['is_corrupted'] ) && $table['is_corrupted'] ) {
847 // Do not search in corrupted tables.
848 continue;
849 }
850
851 foreach ( $search_terms as $search_term ) {
852 if ( ( $table['options']['print_name'] && false !== stripos( $table['name'], $search_term ) )
853 || ( $table['options']['print_description'] && false !== stripos( $table['description'], $search_term ) ) ) {
854 // Found the search term in the name or description (and they are shown).
855 $query_result[ $search_term ][] = $table_id; // Add table ID to result list.
856 // No need to continue searching this search term in this table.
857 continue;
858 }
859
860 // Search search term in visible table cells (without taking Shortcode parameters into account!).
861 foreach ( $table['data'] as $row_idx => $table_row ) {
862 if ( 0 === $table['visibility']['rows'][ $row_idx ] ) {
863 // Row is hidden, so don't search in it.
864 continue;
865 }
866 foreach ( $table_row as $col_idx => $table_cell ) {
867 if ( 0 === $table['visibility']['columns'][ $col_idx ] ) {
868 // Column is hidden, so don't search in it.
869 continue;
870 }
871 // @TODO: Cells are not evaluated here, so math formulas are searched.
872 if ( false !== stripos( $table_cell, $search_term ) ) {
873 // Found the search term in the cell content.
874 $query_result[ $search_term ][] = $table_id; // Add table ID to result list
875 // No need to continue searching this search term in this table.
876 continue 3;
877 }
878 }
879 }
880 }
881 }
882
883 // For all found table IDs for each search term, add additional OR statement to the SQL "WHERE" clause.
884
885 // If $_GET['exact'] is set, WordPress doesn't use % in SQL LIKE clauses.
886 $exact = get_query_var( 'exact' );
887 $n = ( empty( $exact ) ) ? '%' : '';
888 $search_sql = $wpdb->remove_placeholder_escape( $search_sql );
889 foreach ( $query_result as $search_term => $table_ids ) {
890 $search_term = esc_sql( $wpdb->esc_like( $search_term ) );
891 $old_or = "OR ({$wpdb->posts}.post_content LIKE '{$n}{$search_term}{$n}')";
892 $table_ids = implode( '|', $table_ids );
893 $regexp = '\\\\[' . TablePress::$shortcode . ' id=(["\\\']?)(' . $table_ids . ')([\]"\\\' /])'; // ' needs to be single escaped, [ double escaped (with \\) in mySQL
894 $new_or = $old_or . " OR ({$wpdb->posts}.post_content REGEXP '{$regexp}')";
895 $search_sql = str_replace( $old_or, $new_or, $search_sql );
896 }
897 $search_sql = $wpdb->add_placeholder_escape( $search_sql );
898
899 return $search_sql;
900 }
901
902 } // class TablePress_Frontend_Controller
903